@twitterapis/mcp 0.9.7 → 0.9.9
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 +19 -0
- package/README.md +20 -3
- package/package.json +7 -5
- package/src/index.js +36 -17
- package/src/tools.js +27 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.9.9 (2026-09-11)
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- **The server instructions now ask for a control before an "ignored parameter" report.** A generic query on a score-ordered sort returns the site-wide listing, which reads exactly like a dropped parameter and is not one. Before drafting a report that a parameter is ignored or a field is empty, the model is told to re-run with a distinctive value that could only match if the parameter was honoured, and with the phrase quoted, then title the report by what the control showed.
|
|
8
|
+
- **`scripts/prepublish-version-class.mjs`, run by `prepublishOnly`, refuses a patch bump when the tool catalog grew.** It reads the published tarball from npm, counts the catalog on both sides, and blocks a publish whose version is a patch over the published one while the catalog is larger. Fails closed when npm cannot be read. `--selftest` covers both directions offline.
|
|
9
|
+
|
|
10
|
+
## 0.9.8 (2026-09-04)
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **`twitter_feedback_list`, read the feedback reports this account has already sent.** The API shipped `GET /feedback` ("List Feedback") after 0.9.7 and no tool covered it, so `test/openapi-parity.mjs` was failing on `origin/main` against the live spec: 98 tools versus 99 endpoints. Because `prepublishOnly` runs `npm test`, that red suite also meant the package could not be published at all. The vendored `test/openapi.snapshot.json` was one route behind the live spec, which is why `scripts/gen-tools.mjs` could not see the endpoint either; refreshed, and exactly one route changed. Distinct from `twitter_feedback_send` action `"list"`, which shows LOCAL drafts that were never sent: this reads what the server holds. Free per call, `status`/`type` filters, cursor-paged. Catalog is now 99 tools, 63 reads and 36 writes, back to exact parity with the API's own endpoint count. **This tool lands BEFORE the route it calls is deployed, which is the deliberate order (surfaces first, route last), so it must not be PUBLISHED to npm until the backend deploy is live.** Probed 2026-09-05: `GET /feedback` returns a bare `404 Not Found`, byte-identical to a nonexistent path, while `GET /feedback/<uuid>` and `POST /feedback` both answer from the application, so the route is authored (twitterapis-backend `origin/feat/feedback-endpoint`, `07a83ad`) but not yet serving. Re-probe before any publish.
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- **`manifest.json` now declares its tool catalog statically, so the MCPB bundle carries capability metadata a registry can read without running the server.** Reproduced live: our Smithery listing scored 45/100 with "No capabilities found" even though `tools/list` already answers correctly with 98 tools and no API key (the 2026-08-18 lazy-validation fix). The MCPB manifest schema (v0.3) has an optional `tools: [{name, description}]` field plus a `tools_generated` flag for exactly this; it was never populated. A new `scripts/gen-manifest-tools.mjs` derives it from `src/tools.js` (the same generated catalog everything else in this repo is built from), wired into `npm run build`, `npm run bundle`, and `npm test` (`--check` mode) so it cannot go stale the way `src/tools.js` itself is guarded against.
|
|
19
|
+
- **Smithery's own listing for this server was a hard 404 ("Server Not Found or Removed"), not merely showing "No capabilities found" as reported.** Smithery was acquired by Arcade.dev (2026-08-05) and its publish model changed: local/stdio servers now require an explicit `.mcpb` bundle upload (`smithery mcp publish`) rather than an automatic scan of the GitHub repo. The Aug 18 listing did not survive that migration. Republished under `emma-fwab/twitterapis-mcp` (our Smithery org namespace) via the CLI; the listing is live again with correct description, repo, homepage and icon.
|
|
20
|
+
- **Known upstream limitation, not fixable from this repo: Smithery's publish backend rejects a `tools[]` entry that lacks `inputSchema`** ("expected object, received undefined" x98), but the official MCPB manifest schema's `tools` field forbids any key beyond `name`/`description` (`additionalProperties: false`), and `mcpb pack` itself refuses to build a bundle that adds one. Confirmed by testing both directions: a manifest with `inputSchema` fails `mcpb validate` and `mcpb pack` outright; a manifest without it publishes fine but Smithery shows "No capabilities found". Shipping the spec-compliant `name`/`description` list here is still correct (matches the documented format, harmless, and picks up automatically if Smithery relaxes their validator), but full capability display on Smithery is blocked on their side until that's resolved.
|
|
21
|
+
|
|
3
22
|
## 0.9.7 (2026-09-04)
|
|
4
23
|
|
|
5
24
|
### Fixed
|
package/README.md
CHANGED
|
@@ -91,7 +91,7 @@ Restart Claude Desktop. The `twitter_*` tools appear in the tool picker.
|
|
|
91
91
|
|
|
92
92
|
## Tools
|
|
93
93
|
|
|
94
|
-
|
|
94
|
+
99 tools: 63 reads and 36 write actions. Most user endpoints accept `username` (handle without @) **or** `user_id` (`twitter_user_likes` and `twitter_user_tweets_complete` require `user_id`); tweet endpoints accept `id` **or** `url`; paginated endpoints return a `cursor` you pass back to get the next page. Four of the reads are free account lookups (`twitter_account_me`, `twitter_account_payments`, `twitter_feedback_get`, `twitter_feedback_list`); the 14 monitoring tools and `twitter_feedback_send` are also free (account administration, not metered reads).
|
|
95
95
|
|
|
96
96
|
Public reads (search, profiles, tweets, followers, likes) work with just your API key. The **account-only** reads (bookmarks, DMs, home timeline, followers-you-know) and **most write actions** act AS an authenticated X account, so they need a session linked to your key first (returns HTTP 409 until then). Link a session either by registering your x.com cookies (`twitter_customer_session`) or by logging in with a username/password (`twitter_user_login`). Alternatively, pass **per-call inline credentials** on any of those tools (`auth_token` + `ct0`, with optional `proxy_url` / `user_agent`) to act AS that account for a single call without pre-registering a session, so one API key can act as many accounts. For write actions, set `proxy_url` to a residential proxy, since X soft-blocks writes that egress from datacenter IPs. Each write tool is annotated `readOnlyHint: false`; reversing actions (delete, unfollow, unlike, unretweet, unbookmark, monitor/webhook delete) are annotated `destructiveHint: true` so MCP clients can prompt before running them. The **monitoring** and **feedback** tools (see below) are the exception: they administer your twitterapis.com account, not an X session, so they need only your API key, no linked session and no inline credentials.
|
|
97
97
|
|
|
@@ -109,7 +109,7 @@ Public reads (search, profiles, tweets, followers, likes) work with just your AP
|
|
|
109
109
|
| `twitter_check_follow_relationship` | Follow relationship between two user ids (who follows whom) |
|
|
110
110
|
| `twitter_user_tweets` | A user's recent original tweets (replies excluded) |
|
|
111
111
|
| `twitter_user_tweets_and_replies` | A user's full timeline (tweets + replies) |
|
|
112
|
-
| `twitter_user_tweets_complete` | A user's
|
|
112
|
+
| `twitter_user_tweets_complete` | A large batch of a user's tweet history per call (see [paging note](#paging-twitter_user_tweets_complete)) |
|
|
113
113
|
| `twitter_user_media` | Images and videos a user has posted |
|
|
114
114
|
| `twitter_user_mentions` | Recent public tweets mentioning a user |
|
|
115
115
|
| `twitter_user_likes` | Tweets a user has liked (public Likes tab) |
|
|
@@ -214,6 +214,7 @@ Modelled on Claude Code's own feedback tool. When a call fails in a way that is
|
|
|
214
214
|
|---|---|
|
|
215
215
|
| `twitter_feedback_send` | `action: "draft"` (default) queues a report locally and sends nothing; `"list"` shows the queue; `"send"` posts only the drafts you name to `POST /feedback`; `"discard"` drops them |
|
|
216
216
|
| `twitter_feedback_get` | Read a sent report's status (`new`, `triaged`, `shipped`, `declined`) and the team's response |
|
|
217
|
+
| `twitter_feedback_list` | List the reports this account has already sent, newest first, with `status`/`type` filters and a `cursor` to page |
|
|
217
218
|
|
|
218
219
|
### Session setup
|
|
219
220
|
|
|
@@ -265,6 +266,22 @@ url: "https://x.com/karpathy/status/1849....."
|
|
|
265
266
|
First call, `twitter_user_followers`: `{ username: "openai", count: 100 }`
|
|
266
267
|
Second call, pass back the `cursor` from the first response: `{ username: "openai", count: 100, cursor: "<cursor from response>" }`
|
|
267
268
|
|
|
269
|
+
### Paging `twitter_user_tweets_complete`
|
|
270
|
+
|
|
271
|
+
> "Pull @elonmusk's whole tweet history"
|
|
272
|
+
|
|
273
|
+
`twitter_user_tweets_complete` auto-paginates server-side and returns a large batch per call, but it does **not** guarantee the full history in one call. Read the result like this:
|
|
274
|
+
|
|
275
|
+
- **`next_cursor` is the completion signal, not `count`.** Non-null means the history is truncated and more remains. Null means it is genuinely exhausted. The response also carries `has_more`, the same signal as a boolean.
|
|
276
|
+
- **`max` is a minimum target, not a cap.** Pages arrive in whole chunks, so a response may hold up to one page (<=100) more than requested. Live behaviour: `max=10` returned 20, `max=50` returned 60, `max=150` returned 161, and omitting `max` (server default **200**) returned 201. Never assume `count === max`.
|
|
277
|
+
- **Each call also has a server-side wall-clock budget**, so a response can be truncated even when it returned fewer tweets than requested. That is the second reason `count` cannot tell you whether you are done.
|
|
278
|
+
- **Billing is a flat $0.0024 per call**, regardless of how many tweets come back, so fewer large calls cost less than many small ones.
|
|
279
|
+
|
|
280
|
+
To resume, pass the `next_cursor` straight back in as `cursor` and repeat until it comes back null:
|
|
281
|
+
|
|
282
|
+
First call, `twitter_user_tweets_complete`: `{ user_id: "44196397", max: 800 }`
|
|
283
|
+
Then, while `next_cursor` is non-null: `{ user_id: "44196397", max: 800, cursor: "<next_cursor from previous response>" }`
|
|
284
|
+
|
|
268
285
|
### Monitor brand mentions
|
|
269
286
|
|
|
270
287
|
> "Show me recent tweets mentioning @twitterapis"
|
|
@@ -306,7 +323,7 @@ Calls are billed to your twitterapis.com account. Almost every endpoint is $0.00
|
|
|
306
323
|
|
|
307
324
|
**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.
|
|
308
325
|
|
|
309
|
-
**Is it read-only?** No.
|
|
326
|
+
**Is it read-only?** No. 63 read tools work with just your API key; 36 write actions (post, like, retweet, follow, DM, media upload, 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.
|
|
310
327
|
|
|
311
328
|
**Which clients are supported?** Claude Desktop, Cursor, Windsurf, and VS Code (Copilot agent mode), or any Model Context Protocol client.
|
|
312
329
|
|
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.9.
|
|
4
|
+
"version": "0.9.9",
|
|
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",
|
|
@@ -25,16 +25,18 @@
|
|
|
25
25
|
"scripts": {
|
|
26
26
|
"start": "node src/index.js",
|
|
27
27
|
"check": "node --check src/index.js && node --check src/tools.js && node --check src/query.js",
|
|
28
|
-
"build": "node scripts/gen-tools.mjs --write",
|
|
28
|
+
"build": "node scripts/gen-tools.mjs --write && node scripts/gen-manifest-tools.mjs --write",
|
|
29
29
|
"build:check": "node scripts/gen-tools.mjs --check",
|
|
30
30
|
"openapi:refresh": "node scripts/openapi-refresh.mjs",
|
|
31
|
-
"test": "node scripts/gen-tools.mjs --check && node test/gen-tools-endpoints.mjs && node test/catalog-identity.mjs && node test/tools.test.mjs && node test/feedback.test.mjs && node test/smoke.mjs && node test/openapi-parity.mjs && node test/body-mode-parity.mjs && node test/readme-parity.mjs && node test/firewall.mjs && node test/registry-manifests.mjs",
|
|
32
|
-
"prepublishOnly": "npm test && node test/publish-provenance.mjs",
|
|
31
|
+
"test": "node scripts/gen-tools.mjs --check && node scripts/gen-manifest-tools.mjs --check && node test/gen-tools-endpoints.mjs && node test/catalog-identity.mjs && node test/tools.test.mjs && node test/feedback.test.mjs && node test/hint-for.test.mjs && node test/smoke.mjs && node test/openapi-parity.mjs && node test/body-mode-parity.mjs && node test/readme-parity.mjs && node test/firewall.mjs && node test/registry-manifests.mjs && node scripts/__tests__/reconcile-mcp-publish-chain.test.mjs",
|
|
32
|
+
"prepublishOnly": "npm test && node test/publish-provenance.mjs && node scripts/prepublish-version-class.mjs",
|
|
33
33
|
"check:openapi-parity": "node test/openapi-parity.mjs",
|
|
34
34
|
"check:body-mode-parity": "node test/body-mode-parity.mjs",
|
|
35
35
|
"check:firewall": "node test/firewall.mjs",
|
|
36
36
|
"check:registry": "node test/registry-manifests.mjs",
|
|
37
|
-
"
|
|
37
|
+
"check:publish-chain": "node scripts/reconcile-mcp-publish-chain.mjs",
|
|
38
|
+
"check:manifest-tools": "node scripts/gen-manifest-tools.mjs --check",
|
|
39
|
+
"bundle": "node scripts/gen-manifest-tools.mjs --write && npx -y @anthropic-ai/mcpb@2.1.2 pack .",
|
|
38
40
|
"check:mcpb": "npx -y @anthropic-ai/mcpb@2.1.2 validate manifest.json",
|
|
39
41
|
"check:publish-provenance": "node test/publish-provenance.mjs"
|
|
40
42
|
},
|
package/src/index.js
CHANGED
|
@@ -87,6 +87,38 @@ if (!API_KEY) {
|
|
|
87
87
|
// error branch; the tool name is added by the registration wrapper below.
|
|
88
88
|
let lastError = null;
|
|
89
89
|
|
|
90
|
+
// The 404 hint used to be a flat status-only ternary telling EVERY caller that
|
|
91
|
+
// "the user, tweet, or list may have been deleted or the id is wrong". For a
|
|
92
|
+
// feedback id that sentence is simply wrong, and it sends a customer chasing a
|
|
93
|
+
// report id off to look at a tweet. The feedback routes are the first mounted
|
|
94
|
+
// outside the /twitter surface, so they are the first place the assumption is
|
|
95
|
+
// plainly visible; /account/* would have been next.
|
|
96
|
+
//
|
|
97
|
+
// ONLY THE 404 VARIES. Every other status is about the KEY, the CREDIT
|
|
98
|
+
// balance, the caller's SESSION, or OUR service, and each of those reads
|
|
99
|
+
// identically on every endpoint. Adding per-path branches for them would be
|
|
100
|
+
// surface area with no reader.
|
|
101
|
+
const NOT_FOUND_HINTS = [
|
|
102
|
+
[/^\/feedback/, " (not found. No feedback report with that id on this account, and an id from another account will not resolve here. Use the id returned by twitter_feedback_send action=send.)"],
|
|
103
|
+
[/^\/account/, " (not found. That account resource does not exist for this key.)"],
|
|
104
|
+
];
|
|
105
|
+
const DEFAULT_NOT_FOUND_HINT =
|
|
106
|
+
" (not found. The user, tweet, or list may have been deleted or the id is wrong)";
|
|
107
|
+
|
|
108
|
+
export function hintFor(status, path) {
|
|
109
|
+
if (status === 401) return " (invalid or missing API key, verify TWITTERAPIS_KEY at https://www.twitterapis.com/dashboard)";
|
|
110
|
+
if (status === 402) return " (insufficient credits, top up at https://www.twitterapis.com/dashboard)";
|
|
111
|
+
if (status === 403) return " (access forbidden. The resource may be private or your plan does not include this endpoint)";
|
|
112
|
+
if (status === 404) {
|
|
113
|
+
for (const [re, h] of NOT_FOUND_HINTS) if (re.test(path || "")) return h;
|
|
114
|
+
return DEFAULT_NOT_FOUND_HINT;
|
|
115
|
+
}
|
|
116
|
+
if (status === 409) return " (no authenticated X session for this key. Write actions and account-only reads (likes, bookmarks, DMs, home timeline, follow, post) require linking an X account/session to your key first; see https://www.twitterapis.com/dashboard)";
|
|
117
|
+
if (status === 429) return " (rate limited. Wait a few seconds and retry; reduce request frequency or increase TWITTERAPIS_TIMEOUT_MS if needed)";
|
|
118
|
+
if (status >= 500) return " (upstream API error. Retry in a moment; if persistent, check https://www.twitterapis.com/status)";
|
|
119
|
+
return "";
|
|
120
|
+
}
|
|
121
|
+
|
|
90
122
|
async function callEndpoint(path, args, method = "GET", jsonBody = false, pathParams = []) {
|
|
91
123
|
if (!API_KEY) {
|
|
92
124
|
return {
|
|
@@ -158,22 +190,7 @@ async function callEndpoint(path, args, method = "GET", jsonBody = false, pathPa
|
|
|
158
190
|
});
|
|
159
191
|
const body = await res.text();
|
|
160
192
|
if (!res.ok) {
|
|
161
|
-
const hint =
|
|
162
|
-
res.status === 401
|
|
163
|
-
? " (invalid or missing API key, verify TWITTERAPIS_KEY at https://www.twitterapis.com/dashboard)"
|
|
164
|
-
: res.status === 402
|
|
165
|
-
? " (insufficient credits, top up at https://www.twitterapis.com/dashboard)"
|
|
166
|
-
: res.status === 403
|
|
167
|
-
? " (access forbidden. The resource may be private or your plan does not include this endpoint)"
|
|
168
|
-
: res.status === 404
|
|
169
|
-
? " (not found. The user, tweet, or list may have been deleted or the id is wrong)"
|
|
170
|
-
: res.status === 409
|
|
171
|
-
? " (no authenticated X session for this key. Write actions and account-only reads (likes, bookmarks, DMs, home timeline, follow, post) require linking an X account/session to your key first; see https://www.twitterapis.com/dashboard)"
|
|
172
|
-
: res.status === 429
|
|
173
|
-
? " (rate limited. Wait a few seconds and retry; reduce request frequency or increase TWITTERAPIS_TIMEOUT_MS if needed)"
|
|
174
|
-
: res.status >= 500
|
|
175
|
-
? " (upstream API error. Retry in a moment; if persistent, check https://www.twitterapis.com/status)"
|
|
176
|
-
: "";
|
|
193
|
+
const hint = hintFor(res.status, resolvedPath);
|
|
177
194
|
lastError = {
|
|
178
195
|
path: resolvedPath,
|
|
179
196
|
method,
|
|
@@ -212,7 +229,9 @@ const INSTRUCTIONS =
|
|
|
212
229
|
"twitterapis.com MCP server. Read tools cost credits per call (most $0.0008); account, monitoring and feedback tools are free. " +
|
|
213
230
|
"If a twitterapis tool call fails with an error other than 401/402/409/429 and the user has to work around it, if the user asks for something no twitterapis tool covers, " +
|
|
214
231
|
"if a documented field comes back empty or wrong, or if the user is clearly frustrated with a result, draft a report with twitter_feedback_send (action \"draft\"). " +
|
|
215
|
-
"Drafting is local and silent; never send a draft unless the user names it after reviewing action \"list\"."
|
|
232
|
+
"Drafting is local and silent; never send a draft unless the user names it after reviewing action \"list\". " +
|
|
233
|
+
"Before drafting a report that a parameter is IGNORED or a field is EMPTY, re-run the call with a distinctive value that could only match if the parameter was honoured, and with the phrase quoted; " +
|
|
234
|
+
"if either comes back on topic the issue is ranking or matching, so title it that way and say what the control showed.";
|
|
216
235
|
|
|
217
236
|
const server = new McpServer({ name: "twitterapis", version: VERSION }, { instructions: INSTRUCTIONS });
|
|
218
237
|
|
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:
|
|
11
|
+
// Catalog: 99 tools (63 reads, 36 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
|
|
@@ -156,7 +156,7 @@ export const TOOLS = [
|
|
|
156
156
|
name: "twitter_user_tweets",
|
|
157
157
|
path: "/twitter/user/tweets",
|
|
158
158
|
description:
|
|
159
|
-
"Get a user's recent posting timeline. IMPORTANT: this endpoint does NOT filter server-side, so the response routinely includes retweets and replies alongside original posts. Every item carries is_retweet, is_reply and is_quote booleans, so filter client-side on those flags if you need originals only, and read author.username rather than assuming every item was written by the requested user (a retweet's retweeted_tweet holds the original author). Returns tweet text, id, timestamp, and engagement metrics. Paginate with cursor to go further back.
|
|
159
|
+
"Get a user's recent posting timeline. IMPORTANT: this endpoint does NOT filter server-side, so the response routinely includes retweets and replies alongside original posts. Every item carries is_retweet, is_reply and is_quote booleans, so filter client-side on those flags if you need originals only, and read author.username rather than assuming every item was written by the requested user (a retweet's retweeted_tweet holds the original author). Returns tweet text, id, timestamp, and engagement metrics. Paginate with cursor to go further back. To pull a back-catalogue in bulk with fewer round-trips, use twitter_user_tweets_complete (which is also cursor-paged, not one-shot).",
|
|
160
160
|
shape: {
|
|
161
161
|
username: z.string().optional().describe(
|
|
162
162
|
"Twitter/X handle WITHOUT the leading @ (e.g. \"elonmusk\", \"openai\"). Provide exactly one of username or user_id.",
|
|
@@ -196,13 +196,16 @@ export const TOOLS = [
|
|
|
196
196
|
name: "twitter_user_tweets_complete",
|
|
197
197
|
path: "/twitter/user/tweets/complete",
|
|
198
198
|
description:
|
|
199
|
-
"Get a user's
|
|
199
|
+
"Get a large batch of a user's tweet history in one call, auto-paginating server-side across upstream pages. Heavier than twitter_user_tweets; use it to pull a back-catalogue with fewer round-trips. Returns { count, next_cursor, has_more, tweets }. IMPORTANT, this does NOT guarantee the whole history in one call: next_cursor is the completion signal, NOT count. A non-null next_cursor means the history is TRUNCATED and more remains, so call this tool again with cursor set to that value, and repeat until next_cursor is null (has_more is the same signal as a boolean). Each call is bounded by BOTH max and a server-side wall-clock budget, so a response can be truncated even when it returned fewer tweets than you asked for, which is why count must never be used to decide whether you are done. Requires the numeric user_id (resolve a handle first with twitter_user_info). Billed a flat $0.0024 per call regardless of how many tweets come back, so fewer, larger calls are cheaper than many small ones.",
|
|
200
200
|
shape: {
|
|
201
201
|
user_id: z.string().describe(
|
|
202
202
|
"Numeric Twitter/X user id. Required: this endpoint does not accept a username. Resolve a handle to a user_id first with twitter_user_info.",
|
|
203
203
|
),
|
|
204
204
|
max: z.number().int().min(1).max(3200).optional().describe(
|
|
205
|
-
"
|
|
205
|
+
"Target number of tweets to collect in this call. Defaults to 200 when omitted. This is a MINIMUM target, not a hard cap: pages arrive in whole chunks, so a response may contain up to one page (<=100) more than requested (measured live 2026-09-05: max=10 returned 20). Never assume count === max. Twitter's ~3200-per-user history ceiling still applies overall.",
|
|
206
|
+
),
|
|
207
|
+
cursor: z.string().optional().describe(
|
|
208
|
+
"Resume point from a previous response's next_cursor. Omit on the first call. Pass it back to continue collecting where the last call stopped, and keep repeating while next_cursor is non-null.",
|
|
206
209
|
),
|
|
207
210
|
},
|
|
208
211
|
},
|
|
@@ -825,6 +828,26 @@ export const TOOLS = [
|
|
|
825
828
|
),
|
|
826
829
|
},
|
|
827
830
|
},
|
|
831
|
+
{
|
|
832
|
+
name: "twitter_feedback_list",
|
|
833
|
+
path: "/feedback",
|
|
834
|
+
description:
|
|
835
|
+
"List the feedback reports this account has already SENT to twitterapis.com, newest first. Use it when the user asks what they have reported, or to find the server id of an earlier report so twitter_feedback_get can read its full status. NOT the same as twitter_feedback_send action \"list\", which shows local drafts that have not been sent yet. Each item carries id, type, title, area, status (new, triaged, shipped or declined), the team's response if any, created_at and updated_at, and never details or evidence, so paging this can never bulk-export a report's body: read one by id with twitter_feedback_get for that. Page with cursor while next_cursor is non-null. Free per call, and shares a 10-per-minute limit with the other feedback tools.",
|
|
836
|
+
shape: {
|
|
837
|
+
limit: z.number().int().min(1).max(100).optional().describe(
|
|
838
|
+
"Max reports to return, 1 to 100. Defaults to 25. Anything outside that range is rejected with 400 naming limit.",
|
|
839
|
+
),
|
|
840
|
+
cursor: z.string().optional().describe(
|
|
841
|
+
"Opaque continuation token from a previous response's next_cursor. Omit it to start from the newest report. A cursor that cannot be decoded is a 400 naming cursor, never a silently empty page.",
|
|
842
|
+
),
|
|
843
|
+
status: z.enum(["new","triaged","shipped","declined"]).optional().describe(
|
|
844
|
+
"Optional. Return only reports in this state. Anything else is rejected with 400 naming status.",
|
|
845
|
+
),
|
|
846
|
+
type: z.enum(["bug","idea","missing_capability"]).optional().describe(
|
|
847
|
+
"Optional. Return only reports of this kind. Anything else is rejected with 400 naming type.",
|
|
848
|
+
),
|
|
849
|
+
},
|
|
850
|
+
},
|
|
828
851
|
{
|
|
829
852
|
name: "twitter_home_timeline",
|
|
830
853
|
path: "/twitter/user/home_timeline",
|