@twitterapis/mcp 0.6.1 → 0.6.2
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 +21 -0
- package/README.md +18 -4
- package/package.json +2 -1
- package/src/index.js +36 -18
- package/src/tools.js +117 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **Seven new tools, closing the gap between the MCP surface and the endpoints the API serves.** Reads: `twitter_trends` (top trends for a location, by `country` or `woeid`), `twitter_trends_locations` (every location X publishes trends for, each with its WOEID), and `twitter_account_me` / `twitter_account_payments` (your twitterapis.com account details and payment history; both free, and served on the un-prefixed `/account/*` path). Session and write: `twitter_customer_session` (register your x.com cookies against your key), `twitter_user_login` (log in with username/password, plus `totp_secret` for 2FA), and `twitter_media_upload` (upload a base64 image, returns a `media_id` for `twitter_create_tweet`). The catalog is now 47 tools: 33 reads and 14 write actions.
|
|
8
|
+
- **A JSON-request-body transport for the three endpoints whose handler reads one.** `twitter_customer_session`, `twitter_user_login`, and `twitter_media_upload` set `jsonBody: true`, so their arguments are sent in the JSON body rather than the query string, matching the routes that read `c.req.json()`. For these tools the credential fields are the body payload and are not diverted into `x-*` headers.
|
|
9
|
+
- `twitter_user_login` documents its REAL response contract, `{ ok, username, message }`. The account cookies it mints are stored server-side against your key and are never returned to the caller. (The published OpenAPI still describes an `{ auth_token, ct0, twid }` response for this endpoint, which the live handler does not send; a code comment on the tool flags the mismatch for maintainers.)
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- **`twitter_tweet_thread` no longer advertises a `cursor` it ignores.** `/twitter/tweet/thread` returns the whole ordered thread in a single response and accepts only `id`/`url`, so the tool's `cursor` argument and its "paginate with cursor" wording were removed to match the contract (the live `openapi.json` had already dropped `cursor` here).
|
|
14
|
+
- **`twitter_user_about` description refreshed** to cover the fields the endpoint returns today: verification and identity-verification flags, linked website, and X's "About this account" transparency panel (account country, how the account was created, and username-change history).
|
|
15
|
+
- **`test/openapi.snapshot.json` regenerated from the live `openapi.json`**, bringing the vendored offline copy back in sync. It had drifted on 23 endpoints' fields, and now also carries the four new paths and the `Trend` component schemas.
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- The publish firewall now runs on `npm publish` itself, via `prepublishOnly`, not only on a manual `npm test`. A release that skips the test step can no longer reach the registry unchecked. Verified: with a competitor reference reintroduced into the README, `npm publish` aborts before the tarball stage.
|
|
20
|
+
- The firewall no longer carries its own list of banned terms. It delegates to the maintainer's isolation registry, which is the single place those rules live, so the gate and everything else that enforces them cannot drift apart. If the registry cannot be located, the gate fails rather than passing.
|
|
21
|
+
|
|
22
|
+
The two firewall changes above are release tooling only.
|
|
23
|
+
|
|
3
24
|
## 0.6.1 (2026-07-20)
|
|
4
25
|
|
|
5
26
|
### Fixed
|
package/README.md
CHANGED
|
@@ -91,9 +91,9 @@ Restart Claude Desktop. The `twitter_*` tools appear in the tool picker.
|
|
|
91
91
|
|
|
92
92
|
## Tools
|
|
93
93
|
|
|
94
|
-
|
|
94
|
+
47 tools: 33 reads and 14 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. Two of the reads are free account/billing lookups (`twitter_account_me`, `twitter_account_payments`).
|
|
95
95
|
|
|
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 **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.
|
|
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 **all 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) are annotated `destructiveHint: true` so MCP clients can prompt before running them.
|
|
97
97
|
|
|
98
98
|
### Reads
|
|
99
99
|
|
|
@@ -103,7 +103,7 @@ Public reads (search, profiles, tweets, followers, likes) work with just your AP
|
|
|
103
103
|
| `twitter_user_search` | Find user accounts by name or keyword |
|
|
104
104
|
| `twitter_user_info` | Full profile by handle (bio, counts, verification, location) |
|
|
105
105
|
| `twitter_user_info_by_id` | Full profile by numeric user id |
|
|
106
|
-
| `twitter_user_about` | A user's structured About
|
|
106
|
+
| `twitter_user_about` | A user's structured About object (category, professional/business labels, verification + identity-verification flags, joined date, and X's 'About this account' transparency panel) |
|
|
107
107
|
| `twitter_user_affiliates` | Accounts affiliated with an organization profile |
|
|
108
108
|
| `twitter_check_follow_relationship` | Follow relationship between two user ids (who follows whom) |
|
|
109
109
|
| `twitter_user_tweets` | A user's recent original tweets (replies excluded) |
|
|
@@ -128,6 +128,10 @@ Public reads (search, profiles, tweets, followers, likes) work with just your AP
|
|
|
128
128
|
| `twitter_bookmark_search` | Full-text search within your bookmarks _(session)_ |
|
|
129
129
|
| `twitter_dm_list` | Your DM conversations (inbox), read-only _(session)_ |
|
|
130
130
|
| `twitter_dm_conversation` | Messages in one DM conversation, read-only _(session)_ |
|
|
131
|
+
| `twitter_trends` | Current top trends for a location (by `country` or `woeid`) |
|
|
132
|
+
| `twitter_trends_locations` | Every location X has trends for, each with its WOEID |
|
|
133
|
+
| `twitter_account_me` | Your twitterapis.com account: credits, usage, email (free) |
|
|
134
|
+
| `twitter_account_payments` | Your twitterapis.com payment history (free) |
|
|
131
135
|
|
|
132
136
|
### Write actions _(require a linked X session)_
|
|
133
137
|
|
|
@@ -140,6 +144,16 @@ Public reads (search, profiles, tweets, followers, likes) work with just your AP
|
|
|
140
144
|
| `twitter_bookmark_tweet` / `twitter_unbookmark_tweet` | Bookmark / remove bookmark |
|
|
141
145
|
| `twitter_follow_user` / `twitter_unfollow_user` | Follow / unfollow a user by id |
|
|
142
146
|
| `twitter_dm_send` | Send a Direct Message to a user by their numeric `recipient_id` |
|
|
147
|
+
| `twitter_media_upload` | Upload a base64 image, returns a `media_id` for `twitter_create_tweet` |
|
|
148
|
+
|
|
149
|
+
### Session setup
|
|
150
|
+
|
|
151
|
+
Link an X account to your key once, so the account-only reads and write actions act as it (or pass per-call `auth_token`/`ct0` instead).
|
|
152
|
+
|
|
153
|
+
| Tool | What it does |
|
|
154
|
+
|---|---|
|
|
155
|
+
| `twitter_customer_session` | Register your x.com session cookies (`auth_token` + `ct0`) against your key |
|
|
156
|
+
| `twitter_user_login` | Log in with `username` + `password` (+ `totp_secret` for 2FA); stores the session against your key. Returns a confirmation, never the cookies |
|
|
143
157
|
|
|
144
158
|
## Usage examples
|
|
145
159
|
|
|
@@ -221,7 +235,7 @@ Calls are billed to your twitterapis.com account. Almost every endpoint is $0.00
|
|
|
221
235
|
|
|
222
236
|
**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.
|
|
223
237
|
|
|
224
|
-
**Is it read-only?** No.
|
|
238
|
+
**Is it read-only?** No. 33 read tools work with just your API key; 14 write actions (post, like, retweet, follow, DM, media upload) act as a linked X account or per-call inline credentials.
|
|
225
239
|
|
|
226
240
|
**Which clients are supported?** Claude Desktop, Cursor, Windsurf, and VS Code (Copilot agent mode), or any Model Context Protocol client.
|
|
227
241
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@twitterapis/mcp",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.2",
|
|
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",
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
"start": "node src/index.js",
|
|
25
25
|
"check": "node --check src/index.js && node --check src/tools.js",
|
|
26
26
|
"test": "node test/tools.test.mjs && node test/smoke.mjs && node test/openapi-parity.mjs && node test/firewall.mjs",
|
|
27
|
+
"prepublishOnly": "npm test",
|
|
27
28
|
"check:openapi-parity": "node test/openapi-parity.mjs",
|
|
28
29
|
"check:firewall": "node test/firewall.mjs"
|
|
29
30
|
},
|
package/src/index.js
CHANGED
|
@@ -40,18 +40,13 @@ if (!API_KEY) {
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
// ── REST call ────────────────────────────────────────────────────────────────
|
|
43
|
-
//
|
|
44
|
-
// query string, so the same buildQuery path serves both
|
|
45
|
-
// differs
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
// supplied, this one API key acts as that account; otherwise the key's linked
|
|
51
|
-
// session is used. Lets a single key act as many accounts.
|
|
52
|
-
const { auth_token, ct0, user_agent, proxy_url, ...rest } = args || {};
|
|
53
|
-
const q = buildQuery(rest);
|
|
54
|
-
const url = `${BASE_URL}${path}${q ? `?${q}` : ""}`;
|
|
43
|
+
// Most endpoints (GET reads and the simple POST writes alike) read their params
|
|
44
|
+
// from the query string, so the same buildQuery path serves both and only the
|
|
45
|
+
// HTTP method differs. A few POST endpoints (customer/session, user_login,
|
|
46
|
+
// media/upload) instead read a JSON request body; those tools set jsonBody:true
|
|
47
|
+
// and callEndpoint sends the args in the body rather than the query string.
|
|
48
|
+
async function callEndpoint(path, args, method = "GET", jsonBody = false) {
|
|
49
|
+
const all = args || {};
|
|
55
50
|
|
|
56
51
|
const headers = {
|
|
57
52
|
// The API accepts either header; send both for maximum compatibility.
|
|
@@ -60,11 +55,33 @@ async function callEndpoint(path, args, method = "GET") {
|
|
|
60
55
|
accept: "application/json",
|
|
61
56
|
"user-agent": `twitterapis-mcp/${VERSION}`,
|
|
62
57
|
};
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
58
|
+
|
|
59
|
+
let url;
|
|
60
|
+
let reqBody;
|
|
61
|
+
if (jsonBody) {
|
|
62
|
+
// Endpoints whose handler reads a JSON request body (customer/session,
|
|
63
|
+
// user_login, media/upload). Send every arg in the body: for customer/session
|
|
64
|
+
// and user_login the credentials ARE the payload the handler reads from the
|
|
65
|
+
// body, so they must NOT be diverted into x-* headers the way per-call inline
|
|
66
|
+
// creds are on the query-string tools.
|
|
67
|
+
url = `${BASE_URL}${path}`;
|
|
68
|
+
headers["content-type"] = "application/json";
|
|
69
|
+
reqBody = JSON.stringify(all);
|
|
70
|
+
} else {
|
|
71
|
+
// Pull per-call inline credentials out of args so they travel as request
|
|
72
|
+
// headers, never the query string (the API reads x-auth-token / x-ct0; passing
|
|
73
|
+
// them as query params would leak them into URLs and access logs). When
|
|
74
|
+
// supplied, this one API key acts as that account; otherwise the key's linked
|
|
75
|
+
// session is used. Lets a single key act as many accounts.
|
|
76
|
+
const { auth_token, ct0, user_agent, proxy_url, ...rest } = all;
|
|
77
|
+
const q = buildQuery(rest);
|
|
78
|
+
url = `${BASE_URL}${path}${q ? `?${q}` : ""}`;
|
|
79
|
+
if (auth_token && ct0) {
|
|
80
|
+
headers["x-auth-token"] = auth_token;
|
|
81
|
+
headers["x-ct0"] = ct0;
|
|
82
|
+
if (user_agent) headers["x-user-agent"] = user_agent;
|
|
83
|
+
if (proxy_url) headers["x-proxy-url"] = proxy_url;
|
|
84
|
+
}
|
|
68
85
|
}
|
|
69
86
|
|
|
70
87
|
const ctrl = new AbortController();
|
|
@@ -73,6 +90,7 @@ async function callEndpoint(path, args, method = "GET") {
|
|
|
73
90
|
const res = await fetch(url, {
|
|
74
91
|
method,
|
|
75
92
|
headers,
|
|
93
|
+
body: reqBody,
|
|
76
94
|
signal: ctrl.signal,
|
|
77
95
|
});
|
|
78
96
|
const body = await res.text();
|
|
@@ -120,7 +138,7 @@ for (const tool of TOOLS) {
|
|
|
120
138
|
server.registerTool(
|
|
121
139
|
tool.name,
|
|
122
140
|
{ description: tool.description, inputSchema: tool.shape, annotations },
|
|
123
|
-
async (args) => callEndpoint(tool.path, args, method),
|
|
141
|
+
async (args) => callEndpoint(tool.path, args, method, Boolean(tool.jsonBody)),
|
|
124
142
|
);
|
|
125
143
|
}
|
|
126
144
|
|
package/src/tools.js
CHANGED
|
@@ -115,7 +115,7 @@ export const TOOLS = [
|
|
|
115
115
|
name: "twitter_user_about",
|
|
116
116
|
path: "/twitter/user/user_about",
|
|
117
117
|
description:
|
|
118
|
-
"Get a user's 'About'
|
|
118
|
+
"Get a user's full 'About' object: the structured profile facts X surfaces beyond the bio, including account category and professional/business labels, verification and identity-verification flags, joined date, location and linked website, follower/following counts, and X's 'About this account' transparency panel (the account's country, how the account was created, and its username-change history). Provide a username or a user_id. Use this to enrich a profile beyond what twitter_user_info returns.",
|
|
119
119
|
shape: { ...USER_REF },
|
|
120
120
|
},
|
|
121
121
|
{
|
|
@@ -273,8 +273,12 @@ export const TOOLS = [
|
|
|
273
273
|
name: "twitter_tweet_thread",
|
|
274
274
|
path: "/twitter/tweet/thread",
|
|
275
275
|
description:
|
|
276
|
-
"Get all tweets in a thread: the connected chain of tweets posted by the SAME author in sequence (a tweetstorm or numbered thread). Pass any tweet id/url from the thread and the API returns the full ordered sequence
|
|
277
|
-
|
|
276
|
+
"Get all tweets in a thread: the connected chain of tweets posted by the SAME author in sequence (a tweetstorm or numbered thread). Pass any tweet id/url from the thread and the API returns the full ordered sequence in a single call. Does NOT return replies from other users, use twitter_tweet_replies for that. Accepts either the tweet id or its full URL.",
|
|
277
|
+
// No cursor: /twitter/tweet/thread returns the whole ordered thread in one
|
|
278
|
+
// response and takes no pagination param (openapi lists only id/url). The
|
|
279
|
+
// previous ...CURSOR advertised a cursor the endpoint ignores and drove a
|
|
280
|
+
// false "paginate with cursor" claim; removed to match the real contract.
|
|
281
|
+
shape: { ...TWEET_REF },
|
|
278
282
|
},
|
|
279
283
|
{
|
|
280
284
|
name: "twitter_tweet_retweeters",
|
|
@@ -295,6 +299,46 @@ export const TOOLS = [
|
|
|
295
299
|
...PAGINATION,
|
|
296
300
|
},
|
|
297
301
|
},
|
|
302
|
+
// ── Reads: trends ──────────────────────────────────────────────────────────
|
|
303
|
+
{
|
|
304
|
+
name: "twitter_trends",
|
|
305
|
+
path: "/twitter/trends",
|
|
306
|
+
description:
|
|
307
|
+
"Get the current top trends for a location. With no location parameter, returns Worldwide (WOEID 1, X's own default). Pass country (an ISO code or country name, e.g. 'US' or 'Japan') or a numeric woeid from twitter_trends_locations; woeid wins when both are given. Returns the resolved location, the as_of / created_at timestamps, and the ranked trends list. Use count to truncate the list. A location X will not serve returns a 400.",
|
|
308
|
+
shape: {
|
|
309
|
+
country: z.string().optional().describe(
|
|
310
|
+
"Country name or ISO code to get trends for, e.g. 'US' or 'Japan'. Resolved against the trends locations list. Omit for Worldwide.",
|
|
311
|
+
),
|
|
312
|
+
woeid: z.string().optional().describe(
|
|
313
|
+
"Numeric WOEID from twitter_trends_locations. Takes precedence over country when both are supplied.",
|
|
314
|
+
),
|
|
315
|
+
count: z.number().int().min(1).optional().describe(
|
|
316
|
+
"Truncate the returned trends list to at most this many. Omit to return X's full list for the location.",
|
|
317
|
+
),
|
|
318
|
+
},
|
|
319
|
+
},
|
|
320
|
+
{
|
|
321
|
+
name: "twitter_trends_locations",
|
|
322
|
+
path: "/twitter/trends/locations",
|
|
323
|
+
description:
|
|
324
|
+
"List every location X publishes trends for, each with the numeric WOEID to pass back to twitter_trends as woeid. Takes no parameters. Use this to resolve a country or city to its WOEID before requesting trends for that place.",
|
|
325
|
+
shape: {},
|
|
326
|
+
},
|
|
327
|
+
// ── Reads: your twitterapis.com account (billing; not Twitter data) ─────────
|
|
328
|
+
{
|
|
329
|
+
name: "twitter_account_me",
|
|
330
|
+
path: "/account/me",
|
|
331
|
+
description:
|
|
332
|
+
"Get YOUR twitterapis.com account details: email, name, credits remaining, credits used, total requests made, and account creation date. Authenticated by your API key. This is an account read, not Twitter data, and is free (it does not spend credits).",
|
|
333
|
+
shape: {},
|
|
334
|
+
},
|
|
335
|
+
{
|
|
336
|
+
name: "twitter_account_payments",
|
|
337
|
+
path: "/account/payments",
|
|
338
|
+
description:
|
|
339
|
+
"Get YOUR twitterapis.com payment history: the list of top-ups and charges on your account. Authenticated by your API key. This is an account read, not Twitter data, and is free (it does not spend credits).",
|
|
340
|
+
shape: {},
|
|
341
|
+
},
|
|
298
342
|
// ── Reads: authenticated-account surfaces (require a session behind your key) ─
|
|
299
343
|
{
|
|
300
344
|
name: "twitter_home_timeline",
|
|
@@ -482,6 +526,76 @@ export const TOOLS = [
|
|
|
482
526
|
...INLINE,
|
|
483
527
|
},
|
|
484
528
|
},
|
|
529
|
+
// ── Session bootstrap + media: link an X account to your key, then act as it ─
|
|
530
|
+
// Once a session is linked (via twitter_customer_session or twitter_user_login)
|
|
531
|
+
// the authenticated-account reads and the write actions run AS that account.
|
|
532
|
+
// These three send a JSON request body (jsonBody:true), so the fields travel
|
|
533
|
+
// in the body, not the query string, matching the backend routes that read
|
|
534
|
+
// c.req.json().
|
|
535
|
+
{
|
|
536
|
+
name: "twitter_customer_session",
|
|
537
|
+
path: "/twitter/customer/session",
|
|
538
|
+
method: "POST",
|
|
539
|
+
write: true,
|
|
540
|
+
jsonBody: true,
|
|
541
|
+
description:
|
|
542
|
+
"Register YOUR OWN X account session against your API key, so the authenticated-account tools (twitter_home_timeline, twitter_bookmarks, twitter_dm_list, twitter_dm_conversation, twitter_user_likes) and the write tools (twitter_create_tweet, twitter_dm_send, twitter_follow_user, twitter_favorite_tweet, twitter_retweet, twitter_media_upload) act as your account. Provide your x.com session cookies auth_token and ct0 (copy them from a logged-in browser); optionally a user_agent and a residential proxy_url. The cookies are stored server-side against your key and are never returned. Returns ok, the resolved username, and whether the session validated live. Prefer twitter_user_login if you would rather pass a username/password than raw cookies. Most tools also accept auth_token/ct0 per-call without registering.",
|
|
543
|
+
shape: {
|
|
544
|
+
auth_token: z.string().describe(
|
|
545
|
+
"Your x.com auth_token cookie value, from a logged-in browser session. Stored server-side against your key; never returned.",
|
|
546
|
+
),
|
|
547
|
+
ct0: z.string().describe(
|
|
548
|
+
"Your x.com ct0 (CSRF) cookie value, from the same browser session. Paired with auth_token.",
|
|
549
|
+
),
|
|
550
|
+
user_agent: z.string().optional().describe(
|
|
551
|
+
"Optional. Browser User-Agent to send with this session's requests. Defaults to a current Chrome UA.",
|
|
552
|
+
),
|
|
553
|
+
proxy_url: z.string().optional().describe(
|
|
554
|
+
"Optional. HTTP or SOCKS proxy URL to route this session's traffic through, e.g. 'http://user:pass@host:port'.",
|
|
555
|
+
),
|
|
556
|
+
},
|
|
557
|
+
},
|
|
558
|
+
{
|
|
559
|
+
name: "twitter_user_login",
|
|
560
|
+
path: "/twitter/user/user_login",
|
|
561
|
+
method: "POST",
|
|
562
|
+
write: true,
|
|
563
|
+
jsonBody: true,
|
|
564
|
+
// CONTRACT NOTE (maintainers): the published openapi documents an
|
|
565
|
+
// {auth_token, ct0, twid} response for this endpoint. That is WRONG. The
|
|
566
|
+
// live handler (backend routes/user-login.ts) returns {ok, username,
|
|
567
|
+
// message} and stores the minted session server-side; it never returns the
|
|
568
|
+
// cookies. The description below documents the REAL contract, not the
|
|
569
|
+
// openapi's. Fixing the openapi response schema is a docs/website change.
|
|
570
|
+
description:
|
|
571
|
+
"Log in to X with a username and password (plus totp_secret if the account has 2FA) and store the resulting session against your API key, so the authenticated-account reads and the write tools then act as that account. On success returns { ok, username, message }; it does NOT return the session cookies (auth_token/ct0 are minted and kept server-side, never sent back). Typical failures: bad_credentials (401), two_factor_required (400, add totp_secret), captcha_required (422), acid_challenge (409, confirm the login from the account then retry). This handles real account credentials; never log or echo the values you pass.",
|
|
572
|
+
shape: {
|
|
573
|
+
username: z.string().describe(
|
|
574
|
+
"The X account username/handle (without the leading @). Some accounts also accept the login email here.",
|
|
575
|
+
),
|
|
576
|
+
password: z.string().describe(
|
|
577
|
+
"The X account password.",
|
|
578
|
+
),
|
|
579
|
+
totp_secret: z.string().optional().describe(
|
|
580
|
+
"The account's base32 two-factor (TOTP) secret. Required only when the account has 2FA enabled.",
|
|
581
|
+
),
|
|
582
|
+
},
|
|
583
|
+
},
|
|
584
|
+
{
|
|
585
|
+
name: "twitter_media_upload",
|
|
586
|
+
path: "/twitter/media/upload",
|
|
587
|
+
method: "POST",
|
|
588
|
+
write: true,
|
|
589
|
+
jsonBody: true,
|
|
590
|
+
description:
|
|
591
|
+
"Upload an image to X and get a media_id to attach to a tweet via twitter_create_tweet's media_ids. Provide media_data as base64-encoded image bytes. Acts as your registered account session (register first with twitter_customer_session or twitter_user_login, or pass auth_token/ct0 for this call). Returns ok and the media_id. Only base64 image data is supported over this tool's JSON transport.",
|
|
592
|
+
shape: {
|
|
593
|
+
media_data: z.string().describe(
|
|
594
|
+
"Base64-encoded image bytes to upload. Sent in the JSON request body.",
|
|
595
|
+
),
|
|
596
|
+
...INLINE,
|
|
597
|
+
},
|
|
598
|
+
},
|
|
485
599
|
];
|
|
486
600
|
|
|
487
601
|
// Pure query-string builder: drops undefined/null/empty values, URL-encodes the rest.
|