@twitterapis/mcp 0.7.3 → 0.7.6

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,24 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.7.6 (2026-08-16)
4
+
5
+ ### Added
6
+
7
+ - **`twitter_user_status`** (GET /twitter/user/status): check whether a Twitter/X account is alive, suspended, or deleted. Returns `status` as one of `alive` / `suspended` / `not_found` / `unavailable`, plus the numeric `id` when the account is alive and X's own `reason` when it gives one. Use it instead of `twitter_user_info` when the question is whether an account still exists: user info answers a suspended account, a deleted account, and a handle that never existed all the same way, so it cannot tell a ban from a typo. Every outcome is a successful response, so read the `status` field rather than treating a suspension as an error. A protected (private) account counts as alive. 76 tools now: 47 reads and 29 writes.
8
+
9
+ ## 0.7.5 (2026-08-16)
10
+
11
+ ### Fixed
12
+
13
+ - **5 write tools sent every arg as a URL query-string parameter with no request body**, silently failing 100% of calls: `twitter_monitor_create`, `twitter_monitor_update`, `twitter_monitor_webhook_create`, `twitter_x_user_stream_add_user`, `twitter_x_user_stream_remove_user`. Their backend routes read only `c.req.json()` with no query-string fallback, unlike most write endpoints here which accept either. Every call to any of these 5 returned a 400 "Provide `<field>` in the JSON body" error. Now sends `jsonBody: true` so args travel as a real JSON body, matching what the backend actually reads. Found by an independent review, confirmed live against production before and after the fix (see this repo's own test/smoke.mjs pattern).
14
+ - **`twitter_monitor_update`'s `domain_filter` now accepts `null`** (in addition to an empty string) to clear an existing filter, matching its documented "pass an empty string (or null) to clear" behavior, which the schema previously rejected.
15
+
16
+ ## 0.7.4 (2026-08-15)
17
+
18
+ ### Added
19
+
20
+ - **`domain_filter` on `twitter_monitor_create` and `twitter_monitor_update`** (task #30 follow-up): an optional bare hostname or full URL that restricts a monitor's delivery to only the new posts carrying a link to that host or a subdomain of it. Normalized server-side (lowercased, scheme/path/query/fragment/leading `www.`/trailing port stripped), rejected with a 400 on an invalid hostname shape after normalization. Pass an empty string on update to clear an existing filter; omit the field to leave it unchanged. A filtered-out post still advances the monitor's cursor and is never a metered read either way, it just isn't delivered. This param was already live on the backend and unrestricted for every account; it was undocumented until now. No new tool, no catalog count change.
21
+
3
22
  ## 0.7.3 (2026-08-15)
4
23
 
5
24
  ### Added
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
- 75 tools: 46 reads and 29 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`); the 14 monitoring tools are also free (account administration, not metered reads).
94
+ 76 tools: 47 reads and 29 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`); the 14 monitoring tools 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** tools (see below) are the one 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
 
@@ -103,6 +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_status` | Is an account alive, suspended, or deleted |
106
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) |
107
108
  | `twitter_user_affiliates` | Accounts affiliated with an organization profile |
108
109
  | `twitter_check_follow_relationship` | Follow relationship between two user ids (who follows whom) |
@@ -277,7 +278,7 @@ Calls are billed to your twitterapis.com account. Almost every endpoint is $0.00
277
278
 
278
279
  **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.
279
280
 
280
- **Is it read-only?** No. 46 read tools work with just your API key; 29 write actions (post, like, retweet, follow, DM, media upload, article create/edit/publish/delete, monitor/webhook create/update/delete) act as a linked X account or per-call inline credentials, except monitor/webhook CRUD, which is account administration and needs only your API key.
281
+ **Is it read-only?** No. 47 read tools work with just your API key; 29 write actions (post, like, retweet, follow, DM, media upload, article create/edit/publish/delete, monitor/webhook create/update/delete) act as a linked X account or per-call inline credentials, except monitor/webhook CRUD, which is account administration and needs only your API key.
281
282
 
282
283
  **Which clients are supported?** Claude Desktop, Cursor, Windsurf, and VS Code (Copilot agent mode), or any Model Context Protocol client.
283
284
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@twitterapis/mcp",
3
- "version": "0.7.3",
3
+ "version": "0.7.6",
4
4
  "description": "Official MCP server for twitterapis.com, the Twitter/X API (search, users, followers, tweets, threads, lists, likes, bookmarks, DMs) plus write actions (post/like/retweet/follow) as native tools for Claude, Cursor, and any MCP client.",
5
5
  "repository": {
6
6
  "type": "git",
package/src/tools.js CHANGED
@@ -8,12 +8,12 @@
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: 75 tools (46 reads, 29 writes).
11
+ // Catalog: 76 tools (47 reads, 29 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
15
15
  // write actions, reads its params from the query string), except the per-call
16
- // inline credentials, which travel as x-* request headers, the 4
16
+ // inline credentials, which travel as x-* request headers, the 9
17
17
  // jsonBody tools, whose fields travel in a JSON request body, and any arg listed
18
18
  // in pathParams, which is substituted into the URL path (e.g. {id}) instead. A
19
19
  // tool with `method: "POST"` or `method: "DELETE"` is a write that acts on
@@ -86,6 +86,17 @@ export const TOOLS = [
86
86
  ),
87
87
  },
88
88
  },
89
+ {
90
+ name: "twitter_user_status",
91
+ path: "/twitter/user/status",
92
+ description:
93
+ "Check whether a Twitter/X account is alive, suspended, or deleted. Returns a status field that is one of 'alive', 'suspended', 'not_found', or 'unavailable', plus the numeric id when the account is alive and X's own reason when it gives one. Use this instead of twitter_user_info when the QUESTION is whether the account still exists: user info answers a suspended account, a deleted account, and a handle that never existed all the same way, so it cannot tell a ban from a typo. Every outcome here is a successful response, so read the status field rather than treating a suspension as an error. A protected (private) account counts as alive, since protection is a visibility setting and not an account state.",
94
+ shape: {
95
+ userName: z.string().describe(
96
+ "Twitter/X handle WITHOUT the leading @ (e.g. 'elonmusk', 'openai', 'sama').",
97
+ ),
98
+ },
99
+ },
89
100
  {
90
101
  name: "twitter_user_about",
91
102
  path: "/twitter/user/user_about",
@@ -1370,6 +1381,7 @@ export const TOOLS = [
1370
1381
  path: "/twitter/monitor",
1371
1382
  method: "POST",
1372
1383
  write: true,
1384
+ jsonBody: true,
1373
1385
  description:
1374
1386
  "Start watching an X account for new posts. Every new post from that handle is HMAC-signed and delivered to your registered webhook(s) on a shared poll interval (see twitter_monitor_webhook_create to register a delivery URL first). Free: monitor creation is account administration, not a metered read. Returns the new monitor's id, plus its normalized handle, status, and poll_interval_ms.",
1375
1387
  shape: {
@@ -1379,6 +1391,9 @@ export const TOOLS = [
1379
1391
  webhook_ids: z.string().optional().describe(
1380
1392
  "Optional. Comma-separated webhook id(s) from twitter_monitor_webhook_create to restrict this monitor's deliveries to. Omit to deliver to every active webhook on the account (the default).",
1381
1393
  ),
1394
+ domain_filter: z.string().optional().describe(
1395
+ "Optional. A bare hostname ('example.com') or a full URL ('https://example.com/blog') to restrict delivery to only the new posts that link to that host or a subdomain of it (e.g. 'example.com' matches both example.com and blog.example.com). Normalized server-side: lowercased, scheme/path/query/fragment/leading www./trailing :port stripped. Omit for no filter, the default (deliver every new post). Rejected with a 400 if what remains after normalization is not a valid hostname shape. A post with no matching link is filtered out of delivery, never silently dropped: it still advances the monitor's cursor and counts toward the account's tweets_domain_filtered health metric.",
1396
+ ),
1382
1397
  },
1383
1398
  },
1384
1399
  {
@@ -1393,9 +1408,10 @@ export const TOOLS = [
1393
1408
  path: "/twitter/monitor/{id}",
1394
1409
  method: "POST",
1395
1410
  write: true,
1411
+ jsonBody: true,
1396
1412
  pathParams: ["id"],
1397
1413
  description:
1398
- "Partially update an existing monitor: pause or resume it via status, change which webhooks receive its events via webhook_ids, or both in the same call (applied atomically). Resuming a paused monitor re-runs the same capacity and per-account cap checks as creating a new one, since it adds load back to the shared pool. Free per call. Both fields are optional; omit either to leave it unchanged.",
1414
+ "Partially update an existing monitor: pause or resume it via status, change which webhooks receive its events via webhook_ids, change or clear its domain_filter, or any combination in the same call (applied atomically). Resuming a paused monitor re-runs the same capacity and per-account cap checks as creating a new one, since it adds load back to the shared pool. Free per call. All three fields are optional; omit any of them to leave that part unchanged.",
1399
1415
  shape: {
1400
1416
  id: z.string().describe(
1401
1417
  "The monitor's id, from twitter_monitor_create or twitter_monitor_list.",
@@ -1406,6 +1422,9 @@ export const TOOLS = [
1406
1422
  webhook_ids: z.string().optional().describe(
1407
1423
  "Optional. Comma-separated webhook id(s) to restrict delivery to. Pass an empty string to clear the restriction back to 'deliver to every active webhook'. Omit entirely to leave it unchanged.",
1408
1424
  ),
1425
+ domain_filter: z.string().nullable().optional().describe(
1426
+ "Optional. A bare hostname or full URL to restrict delivery to, same shape and normalization as twitter_monitor_create's domain_filter. Pass an empty string (or null) to clear an existing filter back to 'deliver every new post'. Omit entirely to leave the current filter unchanged. Rejected with a 400 if a non-empty value does not normalize to a valid hostname.",
1427
+ ),
1409
1428
  },
1410
1429
  },
1411
1430
  {
@@ -1458,6 +1477,7 @@ export const TOOLS = [
1458
1477
  path: "/oapi/x_user_stream/add_user_to_monitor_tweet",
1459
1478
  method: "POST",
1460
1479
  write: true,
1480
+ jsonBody: true,
1461
1481
  description:
1462
1482
  "Compat drop-in for twitter_monitor_create using an x_user_stream-shaped request/response envelope: watch an X account for new posts, translated onto the same underlying monitor system. Free per call. Prefer twitter_monitor_create for new integrations; this exists for migrating an existing x_user_stream-shaped integration without a rewrite.",
1463
1483
  shape: {
@@ -1472,6 +1492,7 @@ export const TOOLS = [
1472
1492
  method: "POST",
1473
1493
  write: true,
1474
1494
  destructive: true,
1495
+ jsonBody: true,
1475
1496
  description:
1476
1497
  "Compat drop-in for twitter_monitor_delete using an x_user_stream-shaped envelope: stop watching an account. Irreversible. Free per call.",
1477
1498
  shape: {
@@ -1492,6 +1513,7 @@ export const TOOLS = [
1492
1513
  path: "/twitter/webhook",
1493
1514
  method: "POST",
1494
1515
  write: true,
1516
+ jsonBody: true,
1495
1517
  description:
1496
1518
  "Register an HTTPS endpoint to receive signed monitor events. The HMAC signing secret is returned ONLY in this response, store it immediately: it cannot be retrieved again, and it is what you use to verify the X-TwitterAPIs-Signature header on every delivery. Free per call.",
1497
1519
  shape: {