@twitterapis/mcp 0.16.1 → 0.18.0

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 CHANGED
@@ -1,5 +1,25 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.18.0 (2026-10-01)
4
+
5
+ ### Added
6
+
7
+ - **`twitter_user_about_batch`.** The About object (account country, how the account
8
+ was created, username-change history, verification) for up to 100 accounts in one
9
+ call, by `usernames` or `user_ids`. Results come back in request order, each with
10
+ an `about` object or an error code. Billed per account X answered for; items that
11
+ failed on our side are free and safe to retry. Takes `fields` and `compact` like
12
+ every other read.
13
+
14
+ ## 0.17.0 (2026-09-30)
15
+
16
+ ### Added
17
+
18
+ - **`paid_promotion` on the 17 tweet-list tools.** `"only"` keeps tweets X labels
19
+ Paid partnership (`is_paid_promotion` true), `"exclude"` keeps the rest. It filters
20
+ the page the API returned, so `next_cursor` still pages on, and the response carries
21
+ `paid_promotion_filter { mode, kept, removed }`. Same cost as without it.
22
+
3
23
  ## 0.16.1 (2026-09-29)
4
24
 
5
25
  ### Changed
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
- 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. Every data read also takes `fields` (comma-separated dotted paths to keep, e.g. `id,text,author.username`; pagination and envelope keys always survive) and `compact` (`"1"` for a built-in preset of ids, text, counts and author basics), so a model paying per token can trim a page to what it will read. 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).
94
+ 110 tools: 66 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. Every data read also takes `fields` (comma-separated dotted paths to keep, e.g. `id,text,author.username`; pagination and envelope keys always survive) and `compact` (`"1"` for a built-in preset of ids, text, counts and author basics), so a model paying per token can trim a page to what it will read. 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
 
@@ -105,6 +105,7 @@ Public reads (search, profiles, tweets, followers, likes) work with just your AP
105
105
  | `twitter_user_info_by_id` | Full profile by numeric user id |
106
106
  | `twitter_user_status` | Is an account alive, suspended, or deleted |
107
107
  | `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) |
108
+ | `twitter_user_about_batch` | The same About object for up to 100 accounts in one call (usernames or user_ids), billed per account answered; for vetting a list by country or account history |
108
109
  | `twitter_user_affiliates` | Accounts affiliated with an organization profile |
109
110
  | `twitter_check_follow_relationship` | Follow relationship between two user ids (who follows whom) |
110
111
  | `twitter_user_tweets` | A user's recent original tweets (replies excluded) |
@@ -329,7 +330,7 @@ count: 50
329
330
 
330
331
  ## Pricing
331
332
 
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).
333
+ 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). `twitter_user_about_batch` is billed per account answered, $0.0008 each, so one 100-account call costs up to $0.08.
333
334
 
334
335
  ## Links
335
336
 
@@ -342,7 +343,7 @@ Calls are billed to your twitterapis.com account. Most endpoints are $0.0008/cal
342
343
 
343
344
  **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.
344
345
 
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.
346
+ **Is it read-only?** No. 66 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.
346
347
 
347
348
  **Which clients are supported?** Claude Desktop, Cursor, Windsurf, and VS Code (Copilot agent mode), or any Model Context Protocol client.
348
349
 
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.16.1",
4
+ "version": "0.18.0",
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",
@@ -43,7 +43,9 @@
43
43
  "check:manifest-tools": "node scripts/gen-manifest-tools.mjs --check",
44
44
  "bundle": "node scripts/gen-manifest-tools.mjs --write && npx -y @anthropic-ai/mcpb@2.1.2 pack .",
45
45
  "check:mcpb": "npx -y @anthropic-ai/mcpb@2.1.2 validate manifest.json",
46
- "check:publish-provenance": "node test/publish-provenance.mjs"
46
+ "check:publish-provenance": "node test/publish-provenance.mjs",
47
+ "postpublish": "node scripts/registry-drift.mjs --publish",
48
+ "check:registry-drift": "node scripts/registry-drift.mjs"
47
49
  },
48
50
  "dependencies": {
49
51
  "@modelcontextprotocol/sdk": "^1.0.0",
package/src/tools.js CHANGED
@@ -8,7 +8,7 @@
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: 109 tools (65 reads, 44 writes).
11
+ // Catalog: 110 tools (66 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
@@ -55,6 +55,9 @@ export const TOOLS = [
55
55
  compact: z.enum(["1","true"]).optional().describe(
56
56
  "Optional. Set to \"1\" for the built-in compact preset: ids, url, text, created_at, lang, engagement counts, the is_retweet/is_reply/is_quote flags, conversation ids, the author's id/username/name/followers_count/verification, and the quoted or retweeted tweet's id/url/author username. Trims what it recognises and, on its own, never turns a body into {}. Combine with fields to keep extra paths (then only the named paths and the envelope keys survive).",
57
57
  ),
58
+ paid_promotion: z.enum(["only","exclude"]).optional().describe(
59
+ "Optional. Filter this page's tweets by X's Paid partnership label: \"only\" keeps tweets whose is_paid_promotion is true, \"exclude\" keeps the rest. It filters the page X returned and does not fetch more, so a page can hold fewer tweets than asked for, or none, while next_cursor still pages on; the response carries paid_promotion_filter { mode, kept, removed }. A retweet is judged by its own flag, not the retweeted post's. Same cost as without it.",
60
+ ),
58
61
  },
59
62
  },
60
63
  {
@@ -151,6 +154,26 @@ export const TOOLS = [
151
154
  ),
152
155
  },
153
156
  },
157
+ {
158
+ name: "twitter_user_about_batch",
159
+ path: "/twitter/user/user_about/batch",
160
+ description:
161
+ "Get the 'About' object (account country, how the account was created, username-change history, verification and the rest of twitter_user_about) for up to 100 accounts in ONE call. Provide usernames or user_ids as a comma-separated list, never both. Results come back in request order, each with an about object or an error code (not_found is billed; forbidden is free but X refuses that account to everyone; rate_limited and unavailable are free and safe to retry). Billed per account X answered for, so use this instead of looping twitter_user_about when vetting a list of accounts, e.g. checking where a creator's audience sample is based. One batch per API key runs at a time: a second concurrent call gets 429 batch_in_progress (not billed), so run batches one after another. fields/compact apply inside each item's about object (fields=account_based_in); a results.* path returns about: {}.",
162
+ shape: {
163
+ usernames: z.string().optional().describe(
164
+ "Comma-separated handles, with or without the leading @ (e.g. 'openai,naval,sama'). 1 to 100 after duplicates are removed.",
165
+ ),
166
+ user_ids: z.string().optional().describe(
167
+ "Comma-separated numeric user ids (e.g. '44196397,745273'), as an alternative to usernames. 1 to 100.",
168
+ ),
169
+ fields: z.string().optional().describe(
170
+ "Optional. Comma-separated dotted field paths to KEEP in the response, applied to every object in the returned lists and to nested objects (e.g. \"id,text,author.username\"; a list name may prefix a path, \"tweets.id\"; a prefix that lands on an array applies to each element). Pagination and envelope keys (next_cursor, cursor, has_more, count, partial, error, message, reason) always survive; any other top-level key you do not name is dropped. Use it to cut a page down to the fields you will actually read.",
171
+ ),
172
+ compact: z.enum(["1","true"]).optional().describe(
173
+ "Optional. Set to \"1\" for the built-in compact preset: ids, url, text, created_at, lang, engagement counts, the is_retweet/is_reply/is_quote flags, conversation ids, the author's id/username/name/followers_count/verification, and the quoted or retweeted tweet's id/url/author username. Trims what it recognises and, on its own, never turns a body into {}. Combine with fields to keep extra paths (then only the named paths and the envelope keys survive).",
174
+ ),
175
+ },
176
+ },
154
177
  {
155
178
  name: "twitter_user_affiliates",
156
179
  path: "/twitter/user/affiliates",
@@ -224,6 +247,9 @@ export const TOOLS = [
224
247
  compact: z.enum(["1","true"]).optional().describe(
225
248
  "Optional. Set to \"1\" for the built-in compact preset: ids, url, text, created_at, lang, engagement counts, the is_retweet/is_reply/is_quote flags, conversation ids, the author's id/username/name/followers_count/verification, and the quoted or retweeted tweet's id/url/author username. Trims what it recognises and, on its own, never turns a body into {}. Combine with fields to keep extra paths (then only the named paths and the envelope keys survive).",
226
249
  ),
250
+ paid_promotion: z.enum(["only","exclude"]).optional().describe(
251
+ "Optional. Filter this page's tweets by X's Paid partnership label: \"only\" keeps tweets whose is_paid_promotion is true, \"exclude\" keeps the rest. It filters the page X returned and does not fetch more, so a page can hold fewer tweets than asked for, or none, while next_cursor still pages on; the response carries paid_promotion_filter { mode, kept, removed }. A retweet is judged by its own flag, not the retweeted post's. Same cost as without it.",
252
+ ),
227
253
  },
228
254
  },
229
255
  {
@@ -250,6 +276,9 @@ export const TOOLS = [
250
276
  compact: z.enum(["1","true"]).optional().describe(
251
277
  "Optional. Set to \"1\" for the built-in compact preset: ids, url, text, created_at, lang, engagement counts, the is_retweet/is_reply/is_quote flags, conversation ids, the author's id/username/name/followers_count/verification, and the quoted or retweeted tweet's id/url/author username. Trims what it recognises and, on its own, never turns a body into {}. Combine with fields to keep extra paths (then only the named paths and the envelope keys survive).",
252
278
  ),
279
+ paid_promotion: z.enum(["only","exclude"]).optional().describe(
280
+ "Optional. Filter this page's tweets by X's Paid partnership label: \"only\" keeps tweets whose is_paid_promotion is true, \"exclude\" keeps the rest. It filters the page X returned and does not fetch more, so a page can hold fewer tweets than asked for, or none, while next_cursor still pages on; the response carries paid_promotion_filter { mode, kept, removed }. A retweet is judged by its own flag, not the retweeted post's. Same cost as without it.",
281
+ ),
253
282
  },
254
283
  },
255
284
  {
@@ -273,6 +302,9 @@ export const TOOLS = [
273
302
  compact: z.enum(["1","true"]).optional().describe(
274
303
  "Optional. Set to \"1\" for the built-in compact preset: ids, url, text, created_at, lang, engagement counts, the is_retweet/is_reply/is_quote flags, conversation ids, the author's id/username/name/followers_count/verification, and the quoted or retweeted tweet's id/url/author username. Trims what it recognises and, on its own, never turns a body into {}. Combine with fields to keep extra paths (then only the named paths and the envelope keys survive).",
275
304
  ),
305
+ paid_promotion: z.enum(["only","exclude"]).optional().describe(
306
+ "Optional. Filter this page's tweets by X's Paid partnership label: \"only\" keeps tweets whose is_paid_promotion is true, \"exclude\" keeps the rest. It filters the page X returned and does not fetch more, so a page can hold fewer tweets than asked for, or none, while next_cursor still pages on; the response carries paid_promotion_filter { mode, kept, removed }. A retweet is judged by its own flag, not the retweeted post's. Same cost as without it.",
307
+ ),
276
308
  },
277
309
  },
278
310
  {
@@ -299,6 +331,9 @@ export const TOOLS = [
299
331
  compact: z.enum(["1","true"]).optional().describe(
300
332
  "Optional. Set to \"1\" for the built-in compact preset: ids, url, text, created_at, lang, engagement counts, the is_retweet/is_reply/is_quote flags, conversation ids, the author's id/username/name/followers_count/verification, and the quoted or retweeted tweet's id/url/author username. Trims what it recognises and, on its own, never turns a body into {}. Combine with fields to keep extra paths (then only the named paths and the envelope keys survive).",
301
333
  ),
334
+ paid_promotion: z.enum(["only","exclude"]).optional().describe(
335
+ "Optional. Filter this page's tweets by X's Paid partnership label: \"only\" keeps tweets whose is_paid_promotion is true, \"exclude\" keeps the rest. It filters the page X returned and does not fetch more, so a page can hold fewer tweets than asked for, or none, while next_cursor still pages on; the response carries paid_promotion_filter { mode, kept, removed }. A retweet is judged by its own flag, not the retweeted post's. Same cost as without it.",
336
+ ),
302
337
  },
303
338
  },
304
339
  {
@@ -322,6 +357,9 @@ export const TOOLS = [
322
357
  compact: z.enum(["1","true"]).optional().describe(
323
358
  "Optional. Set to \"1\" for the built-in compact preset: ids, url, text, created_at, lang, engagement counts, the is_retweet/is_reply/is_quote flags, conversation ids, the author's id/username/name/followers_count/verification, and the quoted or retweeted tweet's id/url/author username. Trims what it recognises and, on its own, never turns a body into {}. Combine with fields to keep extra paths (then only the named paths and the envelope keys survive).",
324
359
  ),
360
+ paid_promotion: z.enum(["only","exclude"]).optional().describe(
361
+ "Optional. Filter this page's tweets by X's Paid partnership label: \"only\" keeps tweets whose is_paid_promotion is true, \"exclude\" keeps the rest. It filters the page X returned and does not fetch more, so a page can hold fewer tweets than asked for, or none, while next_cursor still pages on; the response carries paid_promotion_filter { mode, kept, removed }. A retweet is judged by its own flag, not the retweeted post's. Same cost as without it.",
362
+ ),
325
363
  },
326
364
  },
327
365
  {
@@ -345,6 +383,9 @@ export const TOOLS = [
345
383
  compact: z.enum(["1","true"]).optional().describe(
346
384
  "Optional. Set to \"1\" for the built-in compact preset: ids, url, text, created_at, lang, engagement counts, the is_retweet/is_reply/is_quote flags, conversation ids, the author's id/username/name/followers_count/verification, and the quoted or retweeted tweet's id/url/author username. Trims what it recognises and, on its own, never turns a body into {}. Combine with fields to keep extra paths (then only the named paths and the envelope keys survive).",
347
385
  ),
386
+ paid_promotion: z.enum(["only","exclude"]).optional().describe(
387
+ "Optional. Filter this page's tweets by X's Paid partnership label: \"only\" keeps tweets whose is_paid_promotion is true, \"exclude\" keeps the rest. It filters the page X returned and does not fetch more, so a page can hold fewer tweets than asked for, or none, while next_cursor still pages on; the response carries paid_promotion_filter { mode, kept, removed }. A retweet is judged by its own flag, not the retweeted post's. Same cost as without it.",
388
+ ),
348
389
  },
349
390
  },
350
391
  {
@@ -553,6 +594,9 @@ export const TOOLS = [
553
594
  compact: z.enum(["1","true"]).optional().describe(
554
595
  "Optional. Set to \"1\" for the built-in compact preset: ids, url, text, created_at, lang, engagement counts, the is_retweet/is_reply/is_quote flags, conversation ids, the author's id/username/name/followers_count/verification, and the quoted or retweeted tweet's id/url/author username. Trims what it recognises and, on its own, never turns a body into {}. Combine with fields to keep extra paths (then only the named paths and the envelope keys survive).",
555
596
  ),
597
+ paid_promotion: z.enum(["only","exclude"]).optional().describe(
598
+ "Optional. Filter this page's tweets by X's Paid partnership label: \"only\" keeps tweets whose is_paid_promotion is true, \"exclude\" keeps the rest. It filters the page X returned and does not fetch more, so a page can hold fewer tweets than asked for, or none, while next_cursor still pages on; the response carries paid_promotion_filter { mode, kept, removed }. A retweet is judged by its own flag, not the retweeted post's. Same cost as without it.",
599
+ ),
556
600
  },
557
601
  },
558
602
  {
@@ -573,6 +617,9 @@ export const TOOLS = [
573
617
  compact: z.enum(["1","true"]).optional().describe(
574
618
  "Optional. Set to \"1\" for the built-in compact preset: ids, url, text, created_at, lang, engagement counts, the is_retweet/is_reply/is_quote flags, conversation ids, the author's id/username/name/followers_count/verification, and the quoted or retweeted tweet's id/url/author username. Trims what it recognises and, on its own, never turns a body into {}. Combine with fields to keep extra paths (then only the named paths and the envelope keys survive).",
575
619
  ),
620
+ paid_promotion: z.enum(["only","exclude"]).optional().describe(
621
+ "Optional. Filter this page's tweets by X's Paid partnership label: \"only\" keeps tweets whose is_paid_promotion is true, \"exclude\" keeps the rest. It filters the page X returned and does not fetch more, so a page can hold fewer tweets than asked for, or none, while next_cursor still pages on; the response carries paid_promotion_filter { mode, kept, removed }. A retweet is judged by its own flag, not the retweeted post's. Same cost as without it.",
622
+ ),
576
623
  },
577
624
  },
578
625
  {
@@ -631,6 +678,9 @@ export const TOOLS = [
631
678
  compact: z.enum(["1","true"]).optional().describe(
632
679
  "Optional. Set to \"1\" for the built-in compact preset: ids, url, text, created_at, lang, engagement counts, the is_retweet/is_reply/is_quote flags, conversation ids, the author's id/username/name/followers_count/verification, and the quoted or retweeted tweet's id/url/author username. Trims what it recognises and, on its own, never turns a body into {}. Combine with fields to keep extra paths (then only the named paths and the envelope keys survive).",
633
680
  ),
681
+ paid_promotion: z.enum(["only","exclude"]).optional().describe(
682
+ "Optional. Filter this page's tweets by X's Paid partnership label: \"only\" keeps tweets whose is_paid_promotion is true, \"exclude\" keeps the rest. It filters the page X returned and does not fetch more, so a page can hold fewer tweets than asked for, or none, while next_cursor still pages on; the response carries paid_promotion_filter { mode, kept, removed }. A retweet is judged by its own flag, not the retweeted post's. Same cost as without it.",
683
+ ),
634
684
  },
635
685
  },
636
686
  {
@@ -712,6 +762,9 @@ export const TOOLS = [
712
762
  compact: z.enum(["1","true"]).optional().describe(
713
763
  "Optional. Set to \"1\" for the built-in compact preset: ids, url, text, created_at, lang, engagement counts, the is_retweet/is_reply/is_quote flags, conversation ids, the author's id/username/name/followers_count/verification, and the quoted or retweeted tweet's id/url/author username. Trims what it recognises and, on its own, never turns a body into {}. Combine with fields to keep extra paths (then only the named paths and the envelope keys survive).",
714
764
  ),
765
+ paid_promotion: z.enum(["only","exclude"]).optional().describe(
766
+ "Optional. Filter this page's tweets by X's Paid partnership label: \"only\" keeps tweets whose is_paid_promotion is true, \"exclude\" keeps the rest. It filters the page X returned and does not fetch more, so a page can hold fewer tweets than asked for, or none, while next_cursor still pages on; the response carries paid_promotion_filter { mode, kept, removed }. A retweet is judged by its own flag, not the retweeted post's. Same cost as without it.",
767
+ ),
715
768
  },
716
769
  },
717
770
  {
@@ -735,6 +788,9 @@ export const TOOLS = [
735
788
  compact: z.enum(["1","true"]).optional().describe(
736
789
  "Optional. Set to \"1\" for the built-in compact preset: ids, url, text, created_at, lang, engagement counts, the is_retweet/is_reply/is_quote flags, conversation ids, the author's id/username/name/followers_count/verification, and the quoted or retweeted tweet's id/url/author username. Trims what it recognises and, on its own, never turns a body into {}. Combine with fields to keep extra paths (then only the named paths and the envelope keys survive).",
737
790
  ),
791
+ paid_promotion: z.enum(["only","exclude"]).optional().describe(
792
+ "Optional. Filter this page's tweets by X's Paid partnership label: \"only\" keeps tweets whose is_paid_promotion is true, \"exclude\" keeps the rest. It filters the page X returned and does not fetch more, so a page can hold fewer tweets than asked for, or none, while next_cursor still pages on; the response carries paid_promotion_filter { mode, kept, removed }. A retweet is judged by its own flag, not the retweeted post's. Same cost as without it.",
793
+ ),
738
794
  },
739
795
  },
740
796
  {
@@ -884,6 +940,9 @@ export const TOOLS = [
884
940
  compact: z.enum(["1","true"]).optional().describe(
885
941
  "Optional. Set to \"1\" for the built-in compact preset: ids, url, text, created_at, lang, engagement counts, the is_retweet/is_reply/is_quote flags, conversation ids, the author's id/username/name/followers_count/verification, and the quoted or retweeted tweet's id/url/author username. Trims what it recognises and, on its own, never turns a body into {}. Combine with fields to keep extra paths (then only the named paths and the envelope keys survive).",
886
942
  ),
943
+ paid_promotion: z.enum(["only","exclude"]).optional().describe(
944
+ "Optional. Filter this page's tweets by X's Paid partnership label: \"only\" keeps tweets whose is_paid_promotion is true, \"exclude\" keeps the rest. It filters the page X returned and does not fetch more, so a page can hold fewer tweets than asked for, or none, while next_cursor still pages on; the response carries paid_promotion_filter { mode, kept, removed }. A retweet is judged by its own flag, not the retweeted post's. Same cost as without it.",
945
+ ),
887
946
  },
888
947
  },
889
948
  {
@@ -1119,6 +1178,9 @@ export const TOOLS = [
1119
1178
  compact: z.enum(["1","true"]).optional().describe(
1120
1179
  "Optional. Set to \"1\" for the built-in compact preset: ids, url, text, created_at, lang, engagement counts, the is_retweet/is_reply/is_quote flags, conversation ids, the author's id/username/name/followers_count/verification, and the quoted or retweeted tweet's id/url/author username. Trims what it recognises and, on its own, never turns a body into {}. Combine with fields to keep extra paths (then only the named paths and the envelope keys survive).",
1121
1180
  ),
1181
+ paid_promotion: z.enum(["only","exclude"]).optional().describe(
1182
+ "Optional. Filter this page's tweets by X's Paid partnership label: \"only\" keeps tweets whose is_paid_promotion is true, \"exclude\" keeps the rest. It filters the page X returned and does not fetch more, so a page can hold fewer tweets than asked for, or none, while next_cursor still pages on; the response carries paid_promotion_filter { mode, kept, removed }. A retweet is judged by its own flag, not the retweeted post's. Same cost as without it.",
1183
+ ),
1122
1184
  },
1123
1185
  },
1124
1186
  {
@@ -1151,6 +1213,9 @@ export const TOOLS = [
1151
1213
  compact: z.enum(["1","true"]).optional().describe(
1152
1214
  "Optional. Set to \"1\" for the built-in compact preset: ids, url, text, created_at, lang, engagement counts, the is_retweet/is_reply/is_quote flags, conversation ids, the author's id/username/name/followers_count/verification, and the quoted or retweeted tweet's id/url/author username. Trims what it recognises and, on its own, never turns a body into {}. Combine with fields to keep extra paths (then only the named paths and the envelope keys survive).",
1153
1215
  ),
1216
+ paid_promotion: z.enum(["only","exclude"]).optional().describe(
1217
+ "Optional. Filter this page's tweets by X's Paid partnership label: \"only\" keeps tweets whose is_paid_promotion is true, \"exclude\" keeps the rest. It filters the page X returned and does not fetch more, so a page can hold fewer tweets than asked for, or none, while next_cursor still pages on; the response carries paid_promotion_filter { mode, kept, removed }. A retweet is judged by its own flag, not the retweeted post's. Same cost as without it.",
1218
+ ),
1154
1219
  },
1155
1220
  },
1156
1221
  {
@@ -1250,6 +1315,9 @@ export const TOOLS = [
1250
1315
  compact: z.enum(["1","true"]).optional().describe(
1251
1316
  "Optional. Set to \"1\" for the built-in compact preset: ids, url, text, created_at, lang, engagement counts, the is_retweet/is_reply/is_quote flags, conversation ids, the author's id/username/name/followers_count/verification, and the quoted or retweeted tweet's id/url/author username. Trims what it recognises and, on its own, never turns a body into {}. Combine with fields to keep extra paths (then only the named paths and the envelope keys survive).",
1252
1317
  ),
1318
+ paid_promotion: z.enum(["only","exclude"]).optional().describe(
1319
+ "Optional. Filter this page's tweets by X's Paid partnership label: \"only\" keeps tweets whose is_paid_promotion is true, \"exclude\" keeps the rest. It filters the page X returned and does not fetch more, so a page can hold fewer tweets than asked for, or none, while next_cursor still pages on; the response carries paid_promotion_filter { mode, kept, removed }. A retweet is judged by its own flag, not the retweeted post's. Same cost as without it.",
1320
+ ),
1253
1321
  },
1254
1322
  },
1255
1323
  {
@@ -1308,6 +1376,9 @@ export const TOOLS = [
1308
1376
  compact: z.enum(["1","true"]).optional().describe(
1309
1377
  "Optional. Set to \"1\" for the built-in compact preset: ids, url, text, created_at, lang, engagement counts, the is_retweet/is_reply/is_quote flags, conversation ids, the author's id/username/name/followers_count/verification, and the quoted or retweeted tweet's id/url/author username. Trims what it recognises and, on its own, never turns a body into {}. Combine with fields to keep extra paths (then only the named paths and the envelope keys survive).",
1310
1378
  ),
1379
+ paid_promotion: z.enum(["only","exclude"]).optional().describe(
1380
+ "Optional. Filter this page's tweets by X's Paid partnership label: \"only\" keeps tweets whose is_paid_promotion is true, \"exclude\" keeps the rest. It filters the page X returned and does not fetch more, so a page can hold fewer tweets than asked for, or none, while next_cursor still pages on; the response carries paid_promotion_filter { mode, kept, removed }. A retweet is judged by its own flag, not the retweeted post's. Same cost as without it.",
1381
+ ),
1311
1382
  },
1312
1383
  },
1313
1384
  {