@twitterapis/mcp 0.16.0 → 0.17.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 +21 -0
- package/package.json +4 -2
- package/src/server.js +31 -9
- package/src/tools.js +51 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.17.0 (2026-09-30)
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **`paid_promotion` on the 17 tweet-list tools.** `"only"` keeps tweets X labels
|
|
8
|
+
Paid partnership (`is_paid_promotion` true), `"exclude"` keeps the rest. It filters
|
|
9
|
+
the page the API returned, so `next_cursor` still pages on, and the response carries
|
|
10
|
+
`paid_promotion_filter { mode, kept, removed }`. Same cost as without it.
|
|
11
|
+
|
|
12
|
+
## 0.16.1 (2026-09-29)
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- **A restart answered in JSON is ridden out too.** The gateway now answers a
|
|
17
|
+
restart with a JSON 503 (`"error": "gateway_restarting"`, `Retry-After`)
|
|
18
|
+
instead of its HTML page; a read retries on either. Any other JSON 503 from
|
|
19
|
+
the API, and that code on any other status, is still never retried.
|
|
20
|
+
- **A cancelled call stops.** When the MCP client cancels a tool call, the
|
|
21
|
+
request in flight is aborted and the wait before a retry ends at once; the
|
|
22
|
+
result says "Request cancelled by the caller." instead of reporting a timeout.
|
|
23
|
+
|
|
3
24
|
## 0.16.0 (2026-09-29)
|
|
4
25
|
|
|
5
26
|
### Changed
|
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.17.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/server.js
CHANGED
|
@@ -204,7 +204,7 @@ export function createServer({
|
|
|
204
204
|
// substitute into the URL template) and callEndpoint splices them into path
|
|
205
205
|
// before building the query string or body, so a pathParams arg never leaks
|
|
206
206
|
// into either.
|
|
207
|
-
async function callEndpoint(path, args, method = "GET", jsonBody = false, pathParams = []) {
|
|
207
|
+
async function callEndpoint(path, args, method = "GET", jsonBody = false, pathParams = [], { signal: callerSignal } = {}) {
|
|
208
208
|
if (!apiKey && !authHeaders) return paywallResult("no_key");
|
|
209
209
|
// Fill {name} URL segments from args and strip those keys, so a pathParams arg
|
|
210
210
|
// (e.g. a monitor/webhook id) never also leaks into the query string or JSON
|
|
@@ -272,16 +272,34 @@ export function createServer({
|
|
|
272
272
|
// socket dropped mid-answer can all happen after the API did the work, and
|
|
273
273
|
// "fetch failed" alone also covers DNS and TLS errors that retrying cannot fix.
|
|
274
274
|
const RETRYABLE_NET = new Set(["ECONNREFUSED"]);
|
|
275
|
-
|
|
276
|
-
|
|
275
|
+
// The gateway answers a restart two ways: nginx's stock HTML 502/503 page,
|
|
276
|
+
// or (since 2026-09-29) its own JSON 503 whose error is "gateway_restarting".
|
|
277
|
+
// Nginx sends that JSON only when it could not reach the API at all, so the
|
|
278
|
+
// API's own JSON errors (which never use that code) are still never retried.
|
|
279
|
+
const gatewayFailure = (status, text) => {
|
|
280
|
+
if ((status === 502 || status === 503) && /^\s*<(!doctype|html)/i.test(text)) return true;
|
|
281
|
+
if (status !== 503) return false;
|
|
282
|
+
try { return JSON.parse(text)?.error === "gateway_restarting"; } catch { return false; }
|
|
283
|
+
};
|
|
284
|
+
// The MCP caller's cancel (notifications/cancelled) aborts the request in
|
|
285
|
+
// flight and any wait between retries, so a cancelled call stops at once.
|
|
286
|
+
const cancelled = () => Boolean(callerSignal?.aborted);
|
|
287
|
+
const pause = (ms) => cancelled() ? Promise.resolve() : new Promise((resolve) => {
|
|
288
|
+
const done = () => { callerSignal?.removeEventListener?.("abort", done); resolve(); };
|
|
289
|
+
Promise.resolve(sleepImpl(ms)).then(done, done);
|
|
290
|
+
callerSignal?.addEventListener?.("abort", done, { once: true });
|
|
291
|
+
});
|
|
277
292
|
const transientNetwork = (err) =>
|
|
278
293
|
!gotResponse && err?.name !== "AbortError" && RETRYABLE_NET.has(err?.cause?.code ?? err?.code);
|
|
279
294
|
let attempt = 0;
|
|
280
295
|
let gotResponse = false;
|
|
281
296
|
for (;;) {
|
|
282
297
|
gotResponse = false;
|
|
298
|
+
if (cancelled()) return { isError: true, content: [{ type: "text", text: "Request cancelled by the caller." }] };
|
|
283
299
|
const ctrl = new AbortController();
|
|
284
300
|
const timer = setTimeout(() => ctrl.abort(), REQUEST_TIMEOUT_MS);
|
|
301
|
+
const onCancel = () => ctrl.abort();
|
|
302
|
+
callerSignal?.addEventListener?.("abort", onCancel, { once: true });
|
|
285
303
|
try {
|
|
286
304
|
const res = await fetchImpl(url, {
|
|
287
305
|
method,
|
|
@@ -291,9 +309,10 @@ export function createServer({
|
|
|
291
309
|
});
|
|
292
310
|
gotResponse = true;
|
|
293
311
|
const body = await res.text();
|
|
294
|
-
if (canRetry && attempt < retryDelaysMs.length && gatewayFailure(res.status, body)) {
|
|
312
|
+
if (canRetry && !cancelled() && attempt < retryDelaysMs.length && gatewayFailure(res.status, body)) {
|
|
295
313
|
clearTimeout(timer);
|
|
296
|
-
|
|
314
|
+
callerSignal?.removeEventListener?.("abort", onCancel);
|
|
315
|
+
await pause(retryDelaysMs[attempt++]);
|
|
297
316
|
continue;
|
|
298
317
|
}
|
|
299
318
|
if (!res.ok) {
|
|
@@ -323,11 +342,13 @@ export function createServer({
|
|
|
323
342
|
lastError = null;
|
|
324
343
|
return { content: [{ type: "text", text: body }] };
|
|
325
344
|
} catch (err) {
|
|
326
|
-
if (canRetry && attempt < retryDelaysMs.length && transientNetwork(err)) {
|
|
345
|
+
if (canRetry && !cancelled() && attempt < retryDelaysMs.length && transientNetwork(err)) {
|
|
327
346
|
clearTimeout(timer);
|
|
328
|
-
|
|
347
|
+
callerSignal?.removeEventListener?.("abort", onCancel);
|
|
348
|
+
await pause(retryDelaysMs[attempt++]);
|
|
329
349
|
continue;
|
|
330
350
|
}
|
|
351
|
+
if (cancelled()) return { isError: true, content: [{ type: "text", text: "Request cancelled by the caller." }] };
|
|
331
352
|
const code = err?.cause?.code ?? err?.code;
|
|
332
353
|
const msg = err?.name === "AbortError"
|
|
333
354
|
? `timed out after ${REQUEST_TIMEOUT_MS}ms`
|
|
@@ -336,6 +357,7 @@ export function createServer({
|
|
|
336
357
|
return { isError: true, content: [{ type: "text", text: `Request failed: ${msg}` }] };
|
|
337
358
|
} finally {
|
|
338
359
|
clearTimeout(timer);
|
|
360
|
+
callerSignal?.removeEventListener?.("abort", onCancel);
|
|
339
361
|
}
|
|
340
362
|
}
|
|
341
363
|
}
|
|
@@ -370,8 +392,8 @@ export function createServer({
|
|
|
370
392
|
handler = LOCAL_HANDLERS[tool.local];
|
|
371
393
|
if (!handler) throw new Error(`[twitterapis-mcp] tool ${tool.name} declares local handler "${tool.local}" but src/server.js has none`);
|
|
372
394
|
} else {
|
|
373
|
-
handler = async (args) => {
|
|
374
|
-
const result = await callEndpoint(tool.path, args, method, Boolean(tool.jsonBody), tool.pathParams || []);
|
|
395
|
+
handler = async (args, extra) => {
|
|
396
|
+
const result = await callEndpoint(tool.path, args, method, Boolean(tool.jsonBody), tool.pathParams || [], { signal: extra?.signal });
|
|
375
397
|
if (result?.isError && lastError) {
|
|
376
398
|
let resolved = null;
|
|
377
399
|
try { resolved = resolvePathParams(tool.path, tool.pathParams || [], args).path; } catch { resolved = null; }
|
package/src/tools.js
CHANGED
|
@@ -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
|
{
|
|
@@ -224,6 +227,9 @@ export const TOOLS = [
|
|
|
224
227
|
compact: z.enum(["1","true"]).optional().describe(
|
|
225
228
|
"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
229
|
),
|
|
230
|
+
paid_promotion: z.enum(["only","exclude"]).optional().describe(
|
|
231
|
+
"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.",
|
|
232
|
+
),
|
|
227
233
|
},
|
|
228
234
|
},
|
|
229
235
|
{
|
|
@@ -250,6 +256,9 @@ export const TOOLS = [
|
|
|
250
256
|
compact: z.enum(["1","true"]).optional().describe(
|
|
251
257
|
"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
258
|
),
|
|
259
|
+
paid_promotion: z.enum(["only","exclude"]).optional().describe(
|
|
260
|
+
"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.",
|
|
261
|
+
),
|
|
253
262
|
},
|
|
254
263
|
},
|
|
255
264
|
{
|
|
@@ -273,6 +282,9 @@ export const TOOLS = [
|
|
|
273
282
|
compact: z.enum(["1","true"]).optional().describe(
|
|
274
283
|
"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
284
|
),
|
|
285
|
+
paid_promotion: z.enum(["only","exclude"]).optional().describe(
|
|
286
|
+
"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.",
|
|
287
|
+
),
|
|
276
288
|
},
|
|
277
289
|
},
|
|
278
290
|
{
|
|
@@ -299,6 +311,9 @@ export const TOOLS = [
|
|
|
299
311
|
compact: z.enum(["1","true"]).optional().describe(
|
|
300
312
|
"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
313
|
),
|
|
314
|
+
paid_promotion: z.enum(["only","exclude"]).optional().describe(
|
|
315
|
+
"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.",
|
|
316
|
+
),
|
|
302
317
|
},
|
|
303
318
|
},
|
|
304
319
|
{
|
|
@@ -322,6 +337,9 @@ export const TOOLS = [
|
|
|
322
337
|
compact: z.enum(["1","true"]).optional().describe(
|
|
323
338
|
"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
339
|
),
|
|
340
|
+
paid_promotion: z.enum(["only","exclude"]).optional().describe(
|
|
341
|
+
"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.",
|
|
342
|
+
),
|
|
325
343
|
},
|
|
326
344
|
},
|
|
327
345
|
{
|
|
@@ -345,6 +363,9 @@ export const TOOLS = [
|
|
|
345
363
|
compact: z.enum(["1","true"]).optional().describe(
|
|
346
364
|
"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
365
|
),
|
|
366
|
+
paid_promotion: z.enum(["only","exclude"]).optional().describe(
|
|
367
|
+
"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.",
|
|
368
|
+
),
|
|
348
369
|
},
|
|
349
370
|
},
|
|
350
371
|
{
|
|
@@ -553,6 +574,9 @@ export const TOOLS = [
|
|
|
553
574
|
compact: z.enum(["1","true"]).optional().describe(
|
|
554
575
|
"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
576
|
),
|
|
577
|
+
paid_promotion: z.enum(["only","exclude"]).optional().describe(
|
|
578
|
+
"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.",
|
|
579
|
+
),
|
|
556
580
|
},
|
|
557
581
|
},
|
|
558
582
|
{
|
|
@@ -573,6 +597,9 @@ export const TOOLS = [
|
|
|
573
597
|
compact: z.enum(["1","true"]).optional().describe(
|
|
574
598
|
"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
599
|
),
|
|
600
|
+
paid_promotion: z.enum(["only","exclude"]).optional().describe(
|
|
601
|
+
"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.",
|
|
602
|
+
),
|
|
576
603
|
},
|
|
577
604
|
},
|
|
578
605
|
{
|
|
@@ -631,6 +658,9 @@ export const TOOLS = [
|
|
|
631
658
|
compact: z.enum(["1","true"]).optional().describe(
|
|
632
659
|
"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
660
|
),
|
|
661
|
+
paid_promotion: z.enum(["only","exclude"]).optional().describe(
|
|
662
|
+
"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.",
|
|
663
|
+
),
|
|
634
664
|
},
|
|
635
665
|
},
|
|
636
666
|
{
|
|
@@ -712,6 +742,9 @@ export const TOOLS = [
|
|
|
712
742
|
compact: z.enum(["1","true"]).optional().describe(
|
|
713
743
|
"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
744
|
),
|
|
745
|
+
paid_promotion: z.enum(["only","exclude"]).optional().describe(
|
|
746
|
+
"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.",
|
|
747
|
+
),
|
|
715
748
|
},
|
|
716
749
|
},
|
|
717
750
|
{
|
|
@@ -735,6 +768,9 @@ export const TOOLS = [
|
|
|
735
768
|
compact: z.enum(["1","true"]).optional().describe(
|
|
736
769
|
"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
770
|
),
|
|
771
|
+
paid_promotion: z.enum(["only","exclude"]).optional().describe(
|
|
772
|
+
"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.",
|
|
773
|
+
),
|
|
738
774
|
},
|
|
739
775
|
},
|
|
740
776
|
{
|
|
@@ -884,6 +920,9 @@ export const TOOLS = [
|
|
|
884
920
|
compact: z.enum(["1","true"]).optional().describe(
|
|
885
921
|
"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
922
|
),
|
|
923
|
+
paid_promotion: z.enum(["only","exclude"]).optional().describe(
|
|
924
|
+
"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.",
|
|
925
|
+
),
|
|
887
926
|
},
|
|
888
927
|
},
|
|
889
928
|
{
|
|
@@ -1119,6 +1158,9 @@ export const TOOLS = [
|
|
|
1119
1158
|
compact: z.enum(["1","true"]).optional().describe(
|
|
1120
1159
|
"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
1160
|
),
|
|
1161
|
+
paid_promotion: z.enum(["only","exclude"]).optional().describe(
|
|
1162
|
+
"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.",
|
|
1163
|
+
),
|
|
1122
1164
|
},
|
|
1123
1165
|
},
|
|
1124
1166
|
{
|
|
@@ -1151,6 +1193,9 @@ export const TOOLS = [
|
|
|
1151
1193
|
compact: z.enum(["1","true"]).optional().describe(
|
|
1152
1194
|
"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
1195
|
),
|
|
1196
|
+
paid_promotion: z.enum(["only","exclude"]).optional().describe(
|
|
1197
|
+
"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.",
|
|
1198
|
+
),
|
|
1154
1199
|
},
|
|
1155
1200
|
},
|
|
1156
1201
|
{
|
|
@@ -1250,6 +1295,9 @@ export const TOOLS = [
|
|
|
1250
1295
|
compact: z.enum(["1","true"]).optional().describe(
|
|
1251
1296
|
"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
1297
|
),
|
|
1298
|
+
paid_promotion: z.enum(["only","exclude"]).optional().describe(
|
|
1299
|
+
"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.",
|
|
1300
|
+
),
|
|
1253
1301
|
},
|
|
1254
1302
|
},
|
|
1255
1303
|
{
|
|
@@ -1308,6 +1356,9 @@ export const TOOLS = [
|
|
|
1308
1356
|
compact: z.enum(["1","true"]).optional().describe(
|
|
1309
1357
|
"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
1358
|
),
|
|
1359
|
+
paid_promotion: z.enum(["only","exclude"]).optional().describe(
|
|
1360
|
+
"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.",
|
|
1361
|
+
),
|
|
1311
1362
|
},
|
|
1312
1363
|
},
|
|
1313
1364
|
{
|