@twitterapis/mcp 0.1.0 → 0.2.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 +32 -0
- package/README.md +153 -26
- package/package.json +8 -3
- package/src/index.js +42 -20
- package/src/tools.js +422 -55
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.2.0 (2026-06-25)
|
|
4
|
+
|
|
5
|
+
### Added (full API parity)
|
|
6
|
+
|
|
7
|
+
- Grew the catalog from 16 to **37 tools**: 27 reads and 10 write actions.
|
|
8
|
+
- New reads: `twitter_user_about`, `twitter_user_affiliates`, `twitter_check_follow_relationship`, `twitter_user_tweets_complete`, `twitter_user_likes`, `twitter_followers_you_know`, `twitter_home_timeline`, `twitter_bookmarks`, `twitter_bookmark_search`, `twitter_dm_list`, `twitter_dm_conversation`.
|
|
9
|
+
- New write actions: `twitter_create_tweet` (with `reply_to` / `quote`), `twitter_delete_tweet`, `twitter_favorite_tweet` / `twitter_unfavorite_tweet`, `twitter_retweet` / `twitter_unretweet`, `twitter_bookmark_tweet` / `twitter_unbookmark_tweet`, `twitter_follow_user` / `twitter_unfollow_user`.
|
|
10
|
+
- Tool annotations: every write is `readOnlyHint: false`; reversing actions (delete, unfollow, unlike, unretweet, unbookmark) are `destructiveHint: true` so MCP clients can prompt before a mutating call.
|
|
11
|
+
- Account-only reads and all writes act AS a linked X session; added an HTTP 409 error hint pointing users to link a session.
|
|
12
|
+
|
|
13
|
+
### No breaking changes
|
|
14
|
+
|
|
15
|
+
All 16 prior tool names, parameter names, and endpoint mappings are unchanged. Existing `npx @twitterapis/mcp@latest` invocations update automatically.
|
|
16
|
+
|
|
17
|
+
## 0.1.1 (2026-06-24)
|
|
18
|
+
|
|
19
|
+
### Improvements
|
|
20
|
+
|
|
21
|
+
- Tool descriptions rewritten. All 16 tool descriptions and parameter hints now use precise, concrete language matched to how MCP clients surface them. Removed hedging phrases, tightened scope statements, and added concrete value hints for paginated parameters (cursor, count limits).
|
|
22
|
+
- Error hints added. Each tool now carries structured error guidance covering the five most common failure codes (401, 402, 403, 404, 429) with a plain-English fix per code, so agents can self-correct without a docs lookup.
|
|
23
|
+
- README optimized. Quick-start, setup matrix (Claude Desktop, Cursor, Windsurf, VS Code), configuration table, full tool reference, usage examples, troubleshooting section, and pricing note all revised for clarity and scannability.
|
|
24
|
+
- GitHub repository established. The package now carries a canonical repository field pointing to github.com/TwitterAPIs/twitterapis-mcp (public, MIT licensed).
|
|
25
|
+
|
|
26
|
+
### No breaking changes
|
|
27
|
+
|
|
28
|
+
All 16 tool names, parameter names, and API endpoint mappings are unchanged. Existing `npx @twitterapis/mcp@latest` invocations update automatically.
|
|
29
|
+
|
|
30
|
+
## 0.1.0
|
|
31
|
+
|
|
32
|
+
First public release of the `@twitterapis/mcp` npm package. 16 read-only Twitter/X tools: search, user info, timeline, followers and following, verified followers, media, mentions, tweet detail, replies, threads, retweeters, and list members.
|
package/README.md
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
# @twitterapis/mcp
|
|
2
2
|
|
|
3
|
-
Official **Model Context Protocol** server for [twitterapis.com](https://www.twitterapis.com)
|
|
3
|
+
Official **Model Context Protocol** server for [twitterapis.com](https://www.twitterapis.com), the Twitter / X API as native tools for Claude, Cursor, Windsurf, and any MCP client. Reads (search, profiles, timelines, followers, DMs) plus write actions (post, like, retweet, follow).
|
|
4
4
|
|
|
5
|
-
Ask your agent to
|
|
5
|
+
Ask your agent to search tweets, pull a user's profile or timeline, list followers/following, fetch thread context, or enumerate list members and it calls the API directly. Every tool maps to a REST endpoint at `https://api.twitterapis.com`; the server holds no state and forwards your API key on each call.
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## Quick start
|
|
8
8
|
|
|
9
|
-
No install needed
|
|
9
|
+
No install needed. Run with `npx`. You need one thing: an API key (free $0.50 in credits, no card required): **[twitterapis.com/signup](https://www.twitterapis.com/signup)**.
|
|
10
|
+
|
|
11
|
+
## Setup
|
|
10
12
|
|
|
11
13
|
### Claude Desktop
|
|
12
14
|
|
|
13
|
-
|
|
15
|
+
Edit `claude_desktop_config.json` (Settings → Developer → Edit Config):
|
|
14
16
|
|
|
15
17
|
```json
|
|
16
18
|
{
|
|
@@ -24,9 +26,11 @@ Add to `claude_desktop_config.json` (Settings → Developer → Edit Config):
|
|
|
24
26
|
}
|
|
25
27
|
```
|
|
26
28
|
|
|
29
|
+
Restart Claude Desktop. The `twitter_*` tools appear in the tool picker.
|
|
30
|
+
|
|
27
31
|
### Cursor
|
|
28
32
|
|
|
29
|
-
`~/.cursor/mcp.json` (or Settings → MCP → Add):
|
|
33
|
+
`~/.cursor/mcp.json` (or Settings → MCP → Add New Server):
|
|
30
34
|
|
|
31
35
|
```json
|
|
32
36
|
{
|
|
@@ -40,48 +44,171 @@ Add to `claude_desktop_config.json` (Settings → Developer → Edit Config):
|
|
|
40
44
|
}
|
|
41
45
|
```
|
|
42
46
|
|
|
43
|
-
|
|
47
|
+
### Windsurf
|
|
48
|
+
|
|
49
|
+
`~/.codeium/windsurf/mcp_config.json`:
|
|
50
|
+
|
|
51
|
+
```json
|
|
52
|
+
{
|
|
53
|
+
"mcpServers": {
|
|
54
|
+
"twitterapis": {
|
|
55
|
+
"command": "npx",
|
|
56
|
+
"args": ["-y", "@twitterapis/mcp@latest"],
|
|
57
|
+
"env": { "TWITTERAPIS_KEY": "YOUR_API_KEY" }
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### VS Code (Copilot / agent mode)
|
|
64
|
+
|
|
65
|
+
`.vscode/mcp.json` in your workspace, or the user-level MCP settings:
|
|
66
|
+
|
|
67
|
+
```json
|
|
68
|
+
{
|
|
69
|
+
"servers": {
|
|
70
|
+
"twitterapis": {
|
|
71
|
+
"type": "stdio",
|
|
72
|
+
"command": "npx",
|
|
73
|
+
"args": ["-y", "@twitterapis/mcp@latest"],
|
|
74
|
+
"env": { "TWITTERAPIS_KEY": "YOUR_API_KEY" }
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
```
|
|
44
79
|
|
|
45
80
|
## Configuration
|
|
46
81
|
|
|
47
82
|
| Env var | Required | Default | Purpose |
|
|
48
83
|
|---|---|---|---|
|
|
49
|
-
| `TWITTERAPIS_KEY` |
|
|
50
|
-
| `TWITTERAPIS_BASE_URL` | | `https://api.twitterapis.com` | Override the API host |
|
|
51
|
-
| `TWITTERAPIS_TIMEOUT_MS` | | `30000` | Per-request timeout |
|
|
84
|
+
| `TWITTERAPIS_KEY` | Yes | (none) | API key from [dashboard](https://www.twitterapis.com/dashboard) |
|
|
85
|
+
| `TWITTERAPIS_BASE_URL` | No | `https://api.twitterapis.com` | Override the API host |
|
|
86
|
+
| `TWITTERAPIS_TIMEOUT_MS` | No | `30000` | Per-request timeout in milliseconds |
|
|
52
87
|
|
|
53
88
|
## Tools
|
|
54
89
|
|
|
55
|
-
|
|
90
|
+
37 tools: 27 reads and 10 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.
|
|
91
|
+
|
|
92
|
+
Public reads (search, profiles, tweets, followers) work with just your API key. The **account-only** reads (likes, bookmarks, DMs, home timeline, followers-you-know) and **all write actions** act AS an authenticated X account, so they need a session linked to your key first (returns HTTP 409 until then). Each write tool is annotated `readOnlyHint: false`; reversing actions (delete, unfollow, unlike, unretweet, unbookmark) are annotated `destructiveHint: true` so MCP clients can prompt before running them.
|
|
93
|
+
|
|
94
|
+
### Reads
|
|
56
95
|
|
|
57
96
|
| Tool | What it does |
|
|
58
97
|
|---|---|
|
|
59
|
-
| `twitter_advanced_search` | Search tweets with X operators (`from:`, `min_faves:`, `
|
|
60
|
-
| `twitter_user_search` |
|
|
61
|
-
| `twitter_user_info` | Full profile by handle |
|
|
62
|
-
| `twitter_user_info_by_id` | Full profile by numeric id |
|
|
63
|
-
| `
|
|
98
|
+
| `twitter_advanced_search` | Search tweets with X operators (`from:`, `min_faves:`, `since:`, `filter:links`, etc.) |
|
|
99
|
+
| `twitter_user_search` | Find user accounts by name or keyword |
|
|
100
|
+
| `twitter_user_info` | Full profile by handle (bio, counts, verification, location) |
|
|
101
|
+
| `twitter_user_info_by_id` | Full profile by numeric user id |
|
|
102
|
+
| `twitter_user_about` | A user's structured About panel (category, professional labels, joined date) |
|
|
103
|
+
| `twitter_user_affiliates` | Accounts affiliated with an organization profile |
|
|
104
|
+
| `twitter_check_follow_relationship` | Follow relationship between two user ids (who follows whom) |
|
|
105
|
+
| `twitter_user_tweets` | A user's recent original tweets (replies excluded) |
|
|
64
106
|
| `twitter_user_tweets_and_replies` | A user's full timeline (tweets + replies) |
|
|
107
|
+
| `twitter_user_tweets_complete` | A user's near-complete tweet history in one auto-paginated call |
|
|
108
|
+
| `twitter_user_media` | Images and videos a user has posted |
|
|
109
|
+
| `twitter_user_mentions` | Recent public tweets mentioning a user |
|
|
110
|
+
| `twitter_user_likes` | Tweets a user has liked (public Likes tab) |
|
|
65
111
|
| `twitter_user_followers` | Accounts that follow a user |
|
|
66
112
|
| `twitter_user_following` | Accounts a user follows |
|
|
67
|
-
| `twitter_user_verified_followers` | A user's verified followers |
|
|
68
|
-
| `
|
|
69
|
-
| `
|
|
70
|
-
| `twitter_tweet_detail` | One tweet's full detail |
|
|
113
|
+
| `twitter_user_verified_followers` | A user's verified followers only |
|
|
114
|
+
| `twitter_followers_you_know` | Followers of a target that your authenticated account also follows |
|
|
115
|
+
| `twitter_tweet_detail` | Single tweet: text, author, metrics, media, quoted/reply context |
|
|
71
116
|
| `twitter_tweet_replies` | Replies to a tweet |
|
|
72
|
-
| `twitter_tweet_thread` |
|
|
117
|
+
| `twitter_tweet_thread` | Full author thread (connected tweet chain by same author) |
|
|
73
118
|
| `twitter_tweet_retweeters` | Accounts that retweeted a tweet |
|
|
74
|
-
| `twitter_list_members` | Members of a List |
|
|
119
|
+
| `twitter_list_members` | Members of a Twitter/X List |
|
|
120
|
+
| `twitter_home_timeline` | Your authenticated account's Home timeline _(session)_ |
|
|
121
|
+
| `twitter_bookmarks` | Your authenticated account's bookmarks _(session)_ |
|
|
122
|
+
| `twitter_bookmark_search` | Full-text search within your bookmarks _(session)_ |
|
|
123
|
+
| `twitter_dm_list` | Your DM conversations (inbox), read-only _(session)_ |
|
|
124
|
+
| `twitter_dm_conversation` | Messages in one DM conversation, read-only _(session)_ |
|
|
125
|
+
|
|
126
|
+
### Write actions _(require a linked X session)_
|
|
127
|
+
|
|
128
|
+
| Tool | What it does |
|
|
129
|
+
|---|---|
|
|
130
|
+
| `twitter_create_tweet` | Post a tweet; set `reply_to` to reply or `quote` to quote-tweet |
|
|
131
|
+
| `twitter_delete_tweet` | Delete one of your tweets (irreversible) |
|
|
132
|
+
| `twitter_favorite_tweet` / `twitter_unfavorite_tweet` | Like / unlike a tweet |
|
|
133
|
+
| `twitter_retweet` / `twitter_unretweet` | Retweet / undo retweet |
|
|
134
|
+
| `twitter_bookmark_tweet` / `twitter_unbookmark_tweet` | Bookmark / remove bookmark |
|
|
135
|
+
| `twitter_follow_user` / `twitter_unfollow_user` | Follow / unfollow a user by id |
|
|
136
|
+
|
|
137
|
+
## Usage examples
|
|
138
|
+
|
|
139
|
+
### Search for trending AI tweets
|
|
140
|
+
|
|
141
|
+
> "Find the most popular tweets about AI agents posted this week"
|
|
142
|
+
|
|
143
|
+
The agent calls `twitter_advanced_search` with:
|
|
144
|
+
```
|
|
145
|
+
query: "AI agents min_faves:200 since:2024-01-01"
|
|
146
|
+
product: "Top"
|
|
147
|
+
count: 20
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Pull a user's recent posts
|
|
151
|
+
|
|
152
|
+
> "Get the last 10 tweets from @sama"
|
|
153
|
+
|
|
154
|
+
The agent calls `twitter_user_tweets` with:
|
|
155
|
+
```
|
|
156
|
+
username: "sama"
|
|
157
|
+
count: 10
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Read a full thread
|
|
161
|
+
|
|
162
|
+
> "Get the full thread for this tweet: https://x.com/karpathy/status/1849....."
|
|
163
|
+
|
|
164
|
+
The agent calls `twitter_tweet_thread` with:
|
|
165
|
+
```
|
|
166
|
+
url: "https://x.com/karpathy/status/1849....."
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Paginate through followers
|
|
170
|
+
|
|
171
|
+
> "List the first 100 followers of @openai, then the next 100"
|
|
172
|
+
|
|
173
|
+
First call, `twitter_user_followers`: `{ username: "openai", count: 100 }`
|
|
174
|
+
Second call, pass back the `cursor` from the first response: `{ username: "openai", count: 100, cursor: "<cursor from response>" }`
|
|
175
|
+
|
|
176
|
+
### Monitor brand mentions
|
|
177
|
+
|
|
178
|
+
> "Show me recent tweets mentioning @twitterapis"
|
|
179
|
+
|
|
180
|
+
The agent calls `twitter_user_mentions` with:
|
|
181
|
+
```
|
|
182
|
+
username: "twitterapis"
|
|
183
|
+
count: 50
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## Troubleshooting
|
|
187
|
+
|
|
188
|
+
**`HTTP 401 (invalid or missing API key)`** Check that `TWITTERAPIS_KEY` is set correctly in your MCP client config and matches the key shown in your [dashboard](https://www.twitterapis.com/dashboard).
|
|
189
|
+
|
|
190
|
+
**`HTTP 402 (insufficient credits)`** Top up at [twitterapis.com/dashboard](https://www.twitterapis.com/dashboard). Your first $0.50 is free at signup.
|
|
191
|
+
|
|
192
|
+
**`HTTP 403 (access forbidden)`** The account or tweet may be private/protected, or your plan does not include this endpoint.
|
|
193
|
+
|
|
194
|
+
**`HTTP 404 (not found)`** The user, tweet, or list may have been deleted, suspended, or the id/handle is wrong.
|
|
195
|
+
|
|
196
|
+
**`HTTP 429 (rate limited)`** Wait a few seconds and retry. If you hit this frequently, add `"TWITTERAPIS_TIMEOUT_MS": "60000"` to your env config and space out bulk requests.
|
|
197
|
+
|
|
198
|
+
**`Request failed: timed out after 30000ms`** The default timeout is 30 s. For large paginated fetches set `TWITTERAPIS_TIMEOUT_MS` to a higher value (e.g. `60000`).
|
|
199
|
+
|
|
200
|
+
**Tools do not appear in Claude / Cursor** Ensure `npx` is on your PATH and Node.js 18+ is installed (`node --version`). Check MCP client logs for startup errors.
|
|
75
201
|
|
|
76
202
|
## Pricing
|
|
77
203
|
|
|
78
|
-
Calls are billed to your twitterapis.com account at the standard read rate (
|
|
204
|
+
Calls are billed to your twitterapis.com account at the standard read rate ($0.0008/call, or $0.04 per 1,000 tweets (each call returns about 20 tweets)); your first $0.50 is free. See [twitterapis.com/pricing](https://www.twitterapis.com/pricing).
|
|
79
205
|
|
|
80
206
|
## Links
|
|
81
207
|
|
|
82
|
-
- Docs
|
|
83
|
-
- Dashboard / keys
|
|
84
|
-
- REST API (
|
|
208
|
+
- Docs: [docs.twitterapis.com](https://docs.twitterapis.com)
|
|
209
|
+
- Dashboard / API keys: [twitterapis.com/dashboard](https://www.twitterapis.com/dashboard)
|
|
210
|
+
- REST API (without MCP): [api.twitterapis.com](https://api.twitterapis.com)
|
|
211
|
+
- Status: [twitterapis.com/status](https://www.twitterapis.com/status)
|
|
85
212
|
|
|
86
213
|
## License
|
|
87
214
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@twitterapis/mcp",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Official MCP server for twitterapis.com
|
|
3
|
+
"version": "0.2.0",
|
|
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
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "https://github.com/TwitterAPIs/twitterapis-mcp.git"
|
|
8
|
+
},
|
|
5
9
|
"type": "module",
|
|
6
10
|
"bin": {
|
|
7
11
|
"twitterapis-mcp": "src/index.js"
|
|
@@ -10,7 +14,8 @@
|
|
|
10
14
|
"files": [
|
|
11
15
|
"src",
|
|
12
16
|
"README.md",
|
|
13
|
-
"LICENSE"
|
|
17
|
+
"LICENSE",
|
|
18
|
+
"CHANGELOG.md"
|
|
14
19
|
],
|
|
15
20
|
"engines": {
|
|
16
21
|
"node": ">=18"
|
package/src/index.js
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// @twitterapis/mcp
|
|
2
|
+
// @twitterapis/mcp, official MCP server for twitterapis.com
|
|
3
3
|
//
|
|
4
|
-
// Exposes the Twitter / X
|
|
5
|
-
// followers/following, tweets, threads,
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
4
|
+
// Exposes the Twitter / X API as native MCP tools for Claude, Cursor, and any
|
|
5
|
+
// MCP client: reads (search, users, followers/following, tweets, threads,
|
|
6
|
+
// lists, mentions, likes, bookmarks, DMs, home timeline) plus write actions
|
|
7
|
+
// (post/delete tweet, like, retweet, bookmark, follow, and their inverses).
|
|
8
|
+
// Each tool is a thin, typed wrapper over a REST endpoint at
|
|
9
|
+
// https://api.twitterapis.com. The server holds no state and forwards your API
|
|
10
|
+
// key on every call. The tool catalog lives in ./tools.js.
|
|
9
11
|
//
|
|
10
12
|
// Config (env):
|
|
11
|
-
// TWITTERAPIS_KEY required
|
|
12
|
-
// TWITTERAPIS_BASE_URL optional
|
|
13
|
-
// TWITTERAPIS_TIMEOUT_MS optional
|
|
13
|
+
// TWITTERAPIS_KEY required. Your key from https://www.twitterapis.com/signup
|
|
14
|
+
// TWITTERAPIS_BASE_URL optional. Defaults to https://api.twitterapis.com
|
|
15
|
+
// TWITTERAPIS_TIMEOUT_MS optional. Per-request timeout (default 30000)
|
|
14
16
|
//
|
|
15
17
|
// Run: npx -y @twitterapis/mcp@latest (stdio transport)
|
|
16
18
|
|
|
@@ -32,7 +34,10 @@ if (!API_KEY) {
|
|
|
32
34
|
}
|
|
33
35
|
|
|
34
36
|
// ── REST call ────────────────────────────────────────────────────────────────
|
|
35
|
-
|
|
37
|
+
// Every endpoint (GET reads and POST writes alike) reads its params from the
|
|
38
|
+
// query string, so the same buildQuery path serves both; only the HTTP method
|
|
39
|
+
// differs per tool.
|
|
40
|
+
async function callEndpoint(path, args, method = "GET") {
|
|
36
41
|
const q = buildQuery(args);
|
|
37
42
|
const url = `${BASE_URL}${path}${q ? `?${q}` : ""}`;
|
|
38
43
|
|
|
@@ -40,13 +45,13 @@ async function callEndpoint(path, args) {
|
|
|
40
45
|
const timer = setTimeout(() => ctrl.abort(), REQUEST_TIMEOUT_MS);
|
|
41
46
|
try {
|
|
42
47
|
const res = await fetch(url, {
|
|
43
|
-
method
|
|
48
|
+
method,
|
|
44
49
|
headers: {
|
|
45
50
|
// The API accepts either header; send both for maximum compatibility.
|
|
46
51
|
Authorization: `Bearer ${API_KEY}`,
|
|
47
52
|
"x-api-key": API_KEY,
|
|
48
53
|
accept: "application/json",
|
|
49
|
-
"user-agent": "twitterapis-mcp/0.
|
|
54
|
+
"user-agent": "twitterapis-mcp/0.2.0",
|
|
50
55
|
},
|
|
51
56
|
signal: ctrl.signal,
|
|
52
57
|
});
|
|
@@ -54,12 +59,20 @@ async function callEndpoint(path, args) {
|
|
|
54
59
|
if (!res.ok) {
|
|
55
60
|
const hint =
|
|
56
61
|
res.status === 401
|
|
57
|
-
? " (
|
|
62
|
+
? " (invalid or missing API key, verify TWITTERAPIS_KEY at https://www.twitterapis.com/dashboard)"
|
|
58
63
|
: res.status === 402
|
|
59
|
-
? " (insufficient credits
|
|
60
|
-
: res.status ===
|
|
61
|
-
? " (
|
|
62
|
-
:
|
|
64
|
+
? " (insufficient credits, top up at https://www.twitterapis.com/dashboard)"
|
|
65
|
+
: res.status === 403
|
|
66
|
+
? " (access forbidden. The resource may be private or your plan does not include this endpoint)"
|
|
67
|
+
: res.status === 404
|
|
68
|
+
? " (not found. The user, tweet, or list may have been deleted or the id is wrong)"
|
|
69
|
+
: res.status === 409
|
|
70
|
+
? " (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)"
|
|
71
|
+
: res.status === 429
|
|
72
|
+
? " (rate limited. Wait a few seconds and retry; reduce request frequency or increase TWITTERAPIS_TIMEOUT_MS if needed)"
|
|
73
|
+
: res.status >= 500
|
|
74
|
+
? " (upstream API error. Retry in a moment; if persistent, check https://www.twitterapis.com/status)"
|
|
75
|
+
: "";
|
|
63
76
|
return { isError: true, content: [{ type: "text", text: `HTTP ${res.status}${hint}: ${body.slice(0, 1200)}` }] };
|
|
64
77
|
}
|
|
65
78
|
return { content: [{ type: "text", text: body }] };
|
|
@@ -72,13 +85,22 @@ async function callEndpoint(path, args) {
|
|
|
72
85
|
}
|
|
73
86
|
|
|
74
87
|
// ── MCP server ───────────────────────────────────────────────────────────────
|
|
75
|
-
const server = new McpServer({ name: "twitterapis", version: "0.
|
|
88
|
+
const server = new McpServer({ name: "twitterapis", version: "0.2.0" });
|
|
76
89
|
|
|
77
90
|
for (const tool of TOOLS) {
|
|
91
|
+
const method = tool.method || "GET";
|
|
92
|
+
// Surface read/write/destructive intent so MCP clients can warn before a
|
|
93
|
+
// mutating call (default = read-only).
|
|
94
|
+
const annotations = {
|
|
95
|
+
title: tool.name,
|
|
96
|
+
readOnlyHint: !tool.write,
|
|
97
|
+
destructiveHint: Boolean(tool.destructive),
|
|
98
|
+
openWorldHint: true,
|
|
99
|
+
};
|
|
78
100
|
server.registerTool(
|
|
79
101
|
tool.name,
|
|
80
|
-
{ description: tool.description, inputSchema: tool.shape },
|
|
81
|
-
async (args) => callEndpoint(tool.path, args),
|
|
102
|
+
{ description: tool.description, inputSchema: tool.shape, annotations },
|
|
103
|
+
async (args) => callEndpoint(tool.path, args, method),
|
|
82
104
|
);
|
|
83
105
|
}
|
|
84
106
|
|
package/src/tools.js
CHANGED
|
@@ -1,72 +1,439 @@
|
|
|
1
1
|
// Tool catalog + pure query-builder for @twitterapis/mcp.
|
|
2
2
|
// Kept separate from the server wiring (index.js) so it can be unit-tested
|
|
3
3
|
// without spawning the stdio transport.
|
|
4
|
+
//
|
|
5
|
+
// Each tool maps 1:1 to a REST endpoint at https://api.twitterapis.com. Tool
|
|
6
|
+
// arg names map 1:1 to endpoint query params (every endpoint, including the
|
|
7
|
+
// POST write actions, reads its params from the query string). A tool with
|
|
8
|
+
// `method: "POST"` is a write that acts on behalf of the authenticated account
|
|
9
|
+
// behind your API key; reads are GET and default when `method` is omitted.
|
|
4
10
|
import { z } from "zod";
|
|
5
11
|
|
|
6
12
|
// ── Shared Zod input-schema fragments ───────────────────────────────────────
|
|
7
13
|
const PAGINATION = {
|
|
8
|
-
count: z.number().int().
|
|
9
|
-
|
|
14
|
+
count: z.number().int().min(1).max(200).optional().describe(
|
|
15
|
+
"Max items to return for this page. Typical range 1 to 200; endpoint default (20) applies if omitted. To page through results, pass the cursor from the previous response.",
|
|
16
|
+
),
|
|
17
|
+
cursor: z.string().optional().describe(
|
|
18
|
+
"Opaque pagination cursor from a previous response's next_cursor field. Omit on the first call; pass on subsequent calls to fetch the next page.",
|
|
19
|
+
),
|
|
10
20
|
};
|
|
11
21
|
const USER_REF = {
|
|
12
|
-
username: z.string().optional().describe(
|
|
13
|
-
|
|
22
|
+
username: z.string().optional().describe(
|
|
23
|
+
'Twitter/X handle WITHOUT the leading @ (e.g. "elonmusk", "openai"). Provide exactly one of username or user_id.',
|
|
24
|
+
),
|
|
25
|
+
user_id: z.string().optional().describe(
|
|
26
|
+
'Numeric Twitter/X user id (e.g. "44196397"). Provide exactly one of username or user_id.',
|
|
27
|
+
),
|
|
14
28
|
};
|
|
15
29
|
const TWEET_REF = {
|
|
16
|
-
id: z.string().optional().describe(
|
|
17
|
-
|
|
30
|
+
id: z.string().optional().describe(
|
|
31
|
+
'Tweet/post numeric id (e.g. "1789012345678901234"). Provide exactly one of id or url.',
|
|
32
|
+
),
|
|
33
|
+
url: z.string().optional().describe(
|
|
34
|
+
'Full tweet URL, e.g. "https://x.com/elonmusk/status/1789012345678901234". Provide exactly one of id or url.',
|
|
35
|
+
),
|
|
36
|
+
};
|
|
37
|
+
// Optional pooled-session selector, shared by every write action. A customer
|
|
38
|
+
// key maps to a default authenticated session; pass account only to target a
|
|
39
|
+
// specific handle in a multi-account pool.
|
|
40
|
+
const ACCOUNT = {
|
|
41
|
+
account: z.string().optional().describe(
|
|
42
|
+
"Optional. The @handle (without @) of the authenticated account to act AS, when your key manages more than one session. Omit to use your key's default session.",
|
|
43
|
+
),
|
|
18
44
|
};
|
|
19
45
|
|
|
20
|
-
// ──
|
|
46
|
+
// ── Tool catalog. Reads are GET (default); writes set method:"POST". ─────────
|
|
47
|
+
// write:true -> action mutates account/Twitter state (annotated readOnlyHint:false)
|
|
48
|
+
// destructive:true -> action removes/reverses state (delete, un-follow/like/RT/bookmark)
|
|
21
49
|
export const TOOLS = [
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
description:
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
description:
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
description:
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
{
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
shape: {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
50
|
+
// ── Reads: search + discovery ──────────────────────────────────────────────
|
|
51
|
+
{
|
|
52
|
+
name: "twitter_advanced_search",
|
|
53
|
+
path: "/twitter/tweet/advanced_search",
|
|
54
|
+
description:
|
|
55
|
+
"Search recent tweets using X's advanced-search operators. Supports from:, to:, since:YYYY-MM-DD, until:YYYY-MM-DD, min_faves:N, min_retweets:N, filter:links, -filter:replies, lang:en, and free-text. Returns tweet text, author info, engagement metrics, and a pagination cursor. Use product='Latest' for chronological results; 'Top' (default) for engagement-ranked. Example queries: 'AI agents min_faves:100', 'from:openai filter:links since:2024-01-01', '#buildinpublic -filter:replies lang:en'.",
|
|
56
|
+
shape: {
|
|
57
|
+
query: z.string().describe(
|
|
58
|
+
"Full advanced-search query string. Supports X operators: from:handle, to:handle, since:YYYY-MM-DD, until:YYYY-MM-DD, min_faves:N, min_retweets:N, filter:links, filter:images, filter:videos, -filter:replies, lang:en, #hashtag, \"exact phrase\". Example: 'from:openai min_faves:500 since:2024-01-01'.",
|
|
59
|
+
),
|
|
60
|
+
product: z.enum(["Top", "Latest", "Media", "People"]).optional().describe(
|
|
61
|
+
"Result ranking mode. 'Latest' = reverse-chronological (best for monitoring). 'Top' = engagement-ranked (best for finding popular tweets, default when omitted). 'Media' = tweets with images/video. 'People' = matching user accounts.",
|
|
62
|
+
),
|
|
63
|
+
...PAGINATION,
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
name: "twitter_user_search",
|
|
68
|
+
path: "/twitter/user/search",
|
|
69
|
+
description:
|
|
70
|
+
"Search for Twitter/X user accounts by name, keyword, or topic. Returns matching profiles (username, display name, bio, follower count, verification status) with a pagination cursor. Use this to discover accounts in a niche, find brand handles, or locate a person when you only know their name.",
|
|
71
|
+
shape: {
|
|
72
|
+
query: z.string().describe(
|
|
73
|
+
"Name, keyword, or topic to search accounts for. Examples: 'OpenAI', 'AI researcher', 'tech founder'.",
|
|
74
|
+
),
|
|
75
|
+
...PAGINATION,
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
name: "twitter_user_info",
|
|
80
|
+
path: "/twitter/user/info",
|
|
81
|
+
description:
|
|
82
|
+
"Get a user's complete public profile by their @handle: display name, bio, follower count, following count, verification status, location, website, account creation date, and pinned tweet. Use this before fetching tweets or followers to confirm the account exists and resolve the numeric user_id.",
|
|
83
|
+
shape: {
|
|
84
|
+
username: z.string().describe(
|
|
85
|
+
"Twitter/X handle WITHOUT the leading @ (e.g. 'elonmusk', 'openai', 'sama').",
|
|
86
|
+
),
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
name: "twitter_user_info_by_id",
|
|
91
|
+
path: "/twitter/user/info_by_id",
|
|
92
|
+
description:
|
|
93
|
+
"Get a user's complete public profile by their numeric user id. Identical response to twitter_user_info. Use this when you already have a user_id from a previous API response and want to avoid a handle lookup.",
|
|
94
|
+
shape: {
|
|
95
|
+
user_id: z.string().describe(
|
|
96
|
+
"Numeric Twitter/X user id (e.g. '44196397' for @elonmusk). Found in responses from other tools as user_id or author_id.",
|
|
97
|
+
),
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
name: "twitter_user_about",
|
|
102
|
+
path: "/twitter/user/user_about",
|
|
103
|
+
description:
|
|
104
|
+
"Get a user's 'About' panel: the structured profile facts X surfaces beyond the bio, such as account category, professional/business labels, joined date, and location when present. Provide a username or a user_id. Use this to enrich a profile beyond what twitter_user_info returns.",
|
|
105
|
+
shape: { ...USER_REF },
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
name: "twitter_user_affiliates",
|
|
109
|
+
path: "/twitter/user/affiliates",
|
|
110
|
+
description:
|
|
111
|
+
"List the affiliated accounts of an organization profile (the smaller accounts X displays under a company's 'Affiliated' badge, e.g. employees or sub-brands). Provide a username or user_id. Returns profile data per affiliate plus a pagination cursor. Returns empty for accounts with no affiliations.",
|
|
112
|
+
shape: {
|
|
113
|
+
...USER_REF,
|
|
114
|
+
team: z.string().optional().describe(
|
|
115
|
+
"Optional team/sub-group name to filter affiliates by, when the org exposes named teams.",
|
|
116
|
+
),
|
|
117
|
+
...PAGINATION,
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
name: "twitter_check_follow_relationship",
|
|
122
|
+
path: "/twitter/user/check_follow_relationship",
|
|
123
|
+
description:
|
|
124
|
+
"Check the follow relationship between two accounts by numeric user id: whether the source follows the target, whether the target follows the source, blocking/muting flags where available. Both ids are required. Use this to verify a follow before/after a follow action, or to detect mutuals.",
|
|
125
|
+
shape: {
|
|
126
|
+
source_user_id: z.string().describe(
|
|
127
|
+
"Numeric user id of the SOURCE account (the 'is this account following...' subject).",
|
|
128
|
+
),
|
|
129
|
+
target_user_id: z.string().describe(
|
|
130
|
+
"Numeric user id of the TARGET account (the '...the target?' object).",
|
|
131
|
+
),
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
// ── Reads: a user's tweets / timeline ──────────────────────────────────────
|
|
135
|
+
{
|
|
136
|
+
name: "twitter_user_tweets",
|
|
137
|
+
path: "/twitter/user/tweets",
|
|
138
|
+
description:
|
|
139
|
+
"Get a user's recent original tweets, excluding replies and retweets. Returns tweet text, id, timestamp, and engagement metrics. Paginate with cursor to go further back. Use this to analyse a user's own content, opinions, or posting cadence. For replies too, use twitter_user_tweets_and_replies; for the full back-catalogue in one call, use twitter_user_tweets_complete.",
|
|
140
|
+
shape: { ...USER_REF, ...PAGINATION },
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
name: "twitter_user_tweets_and_replies",
|
|
144
|
+
path: "/twitter/user/tweets_and_replies",
|
|
145
|
+
description:
|
|
146
|
+
"Get a user's full activity timeline: their original tweets AND replies to others. Useful for understanding how someone engages with a community, not just what they post. Paginate with cursor. To see only original tweets, use twitter_user_tweets.",
|
|
147
|
+
shape: { ...USER_REF, ...PAGINATION },
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
name: "twitter_user_tweets_complete",
|
|
151
|
+
path: "/twitter/user/tweets/complete",
|
|
152
|
+
description:
|
|
153
|
+
"Get a user's near-complete original-tweet history in a single call, auto-paginating server-side up to a cap (Twitter's ~3200-tweet per-user ceiling). Heavier than twitter_user_tweets; use when you want the whole back-catalogue at once rather than page-by-page. Returns a flat tweet array. Requires the numeric user_id (resolve a handle first with twitter_user_info).",
|
|
154
|
+
shape: {
|
|
155
|
+
user_id: z.string().describe(
|
|
156
|
+
"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.",
|
|
157
|
+
),
|
|
158
|
+
max: z.number().int().min(1).max(3200).optional().describe(
|
|
159
|
+
"Maximum number of tweets to collect (default 800, hard ceiling 3200). Higher values take longer and cost more.",
|
|
160
|
+
),
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
name: "twitter_user_media",
|
|
165
|
+
path: "/twitter/user/media",
|
|
166
|
+
description:
|
|
167
|
+
"Get the images and videos a user has posted. Returns media-containing tweets with URLs to the media files, dimensions, and type (photo/video/animated_gif). Paginate with cursor. Use this to pull a user's visual content history.",
|
|
168
|
+
shape: { ...USER_REF, ...PAGINATION },
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
name: "twitter_user_mentions",
|
|
172
|
+
path: "/twitter/user/mentions",
|
|
173
|
+
description:
|
|
174
|
+
"Get recent public tweets that mention (@ tag) a user. Searches for tweets directed at the username using the to: operator. Returns matching tweets with author info and metrics. Paginate with cursor. Use this to monitor brand mentions, replies directed at an account, or public conversations about a person.",
|
|
175
|
+
shape: {
|
|
176
|
+
username: z.string().describe(
|
|
177
|
+
"Twitter/X handle WITHOUT the leading @ of the user to find mentions for (e.g. 'openai' to find tweets mentioning @openai).",
|
|
178
|
+
),
|
|
179
|
+
...PAGINATION,
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
name: "twitter_user_likes",
|
|
184
|
+
path: "/twitter/user/likes",
|
|
185
|
+
description:
|
|
186
|
+
"Get the tweets a user has liked (their public Likes tab), most recent first. Returns each liked tweet with author and metrics, plus a pagination cursor. Use this to infer interests or find content a user has endorsed. Returns empty if the account hides its likes. Requires the numeric user_id (resolve a handle first with twitter_user_info).",
|
|
187
|
+
shape: {
|
|
188
|
+
user_id: z.string().describe(
|
|
189
|
+
"Numeric Twitter/X user id (e.g. '44196397'). Required: this endpoint does not accept a username. Resolve a handle to a user_id first with twitter_user_info.",
|
|
190
|
+
),
|
|
191
|
+
...PAGINATION,
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
// ── Reads: followers / following graph ─────────────────────────────────────
|
|
195
|
+
{
|
|
196
|
+
name: "twitter_user_followers",
|
|
197
|
+
path: "/twitter/user/followers",
|
|
198
|
+
description:
|
|
199
|
+
"List the accounts that follow a given user. Returns profile data for each follower (username, display name, bio, follower count). Paginate with cursor for large audiences. Useful for audience analysis, finding who follows a brand or influencer.",
|
|
200
|
+
shape: { ...USER_REF, ...PAGINATION },
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
name: "twitter_user_following",
|
|
204
|
+
path: "/twitter/user/following",
|
|
205
|
+
description:
|
|
206
|
+
"List the accounts that a given user follows. Returns profile data for each account followed. Paginate with cursor. Useful for mapping a user's information sources, influencer networks, or competitor monitoring lists.",
|
|
207
|
+
shape: { ...USER_REF, ...PAGINATION },
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
name: "twitter_user_verified_followers",
|
|
211
|
+
path: "/twitter/user/verified_followers",
|
|
212
|
+
description:
|
|
213
|
+
"List a user's followers who have a verified account (checkmark). Filters the follower list to verified accounts only, useful for identifying notable or institutional followers. Paginate with cursor.",
|
|
214
|
+
shape: { ...USER_REF, ...PAGINATION },
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
name: "twitter_followers_you_know",
|
|
218
|
+
path: "/twitter/user/followers_you_know",
|
|
219
|
+
description:
|
|
220
|
+
"List the 'Followers you know' for a target user id: the followers of that account that YOUR authenticated account also follows (mutual-connection overlap). Requires an authenticated session behind your key. Returns profile data per overlap account plus a cursor.",
|
|
221
|
+
shape: {
|
|
222
|
+
user_id: z.string().describe(
|
|
223
|
+
"Numeric user id of the target account to compute shared followers against.",
|
|
224
|
+
),
|
|
225
|
+
...PAGINATION,
|
|
226
|
+
},
|
|
227
|
+
},
|
|
228
|
+
// ── Reads: a single tweet + its conversation ───────────────────────────────
|
|
229
|
+
{
|
|
230
|
+
name: "twitter_tweet_detail",
|
|
231
|
+
path: "/twitter/tweet/detail",
|
|
232
|
+
description:
|
|
233
|
+
"Get the full detail of a single tweet: text, author profile, post timestamp, like/retweet/reply/quote counts, attached media, referenced quoted tweet, and parent reply context. Use this to inspect a specific tweet before fetching its replies or thread. Accepts either the tweet id or its full URL.",
|
|
234
|
+
shape: { ...TWEET_REF },
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
name: "twitter_tweet_replies",
|
|
238
|
+
path: "/twitter/tweet/replies",
|
|
239
|
+
description:
|
|
240
|
+
"Get replies to a specific tweet. Returns each reply tweet with author, text, and metrics. Paginate with cursor to load more. Use this to read the conversation under a tweet, gauge sentiment, or find notable responses.",
|
|
241
|
+
shape: { ...TWEET_REF, ...PAGINATION },
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
name: "twitter_tweet_thread",
|
|
245
|
+
path: "/twitter/tweet/thread",
|
|
246
|
+
description:
|
|
247
|
+
"Get all tweets in a thread: the connected chain of tweets posted by the SAME author in sequence (a tweetstorm or numbered thread). Pass any tweet id/url from the thread and the API returns the full ordered sequence. Paginate with cursor for long threads. Does NOT return replies from other users, use twitter_tweet_replies for that.",
|
|
248
|
+
shape: { ...TWEET_REF, ...PAGINATION },
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
name: "twitter_tweet_retweeters",
|
|
252
|
+
path: "/twitter/tweet/retweeters",
|
|
253
|
+
description:
|
|
254
|
+
"List the accounts that retweeted a specific tweet. Returns profile data for each retweeter. Paginate with cursor. Useful for finding who amplified a piece of content or mapping a tweet's distribution network.",
|
|
255
|
+
shape: { ...TWEET_REF, ...PAGINATION },
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
name: "twitter_list_members",
|
|
259
|
+
path: "/twitter/list/members",
|
|
260
|
+
description:
|
|
261
|
+
"List the members of a Twitter/X List by its numeric list id. Returns profile data for each member. Paginate with cursor. Use this to enumerate curated account sets, including competitor lists, industry watchlists, or media outlet lists. The list_id appears in the X.com list URL (x.com/i/lists/<list_id>).",
|
|
262
|
+
shape: {
|
|
263
|
+
list_id: z.string().describe(
|
|
264
|
+
"Numeric Twitter/X List id. Found in the list URL: x.com/i/lists/<list_id>.",
|
|
265
|
+
),
|
|
266
|
+
...PAGINATION,
|
|
267
|
+
},
|
|
268
|
+
},
|
|
269
|
+
// ── Reads: authenticated-account surfaces (require a session behind your key) ─
|
|
270
|
+
{
|
|
271
|
+
name: "twitter_home_timeline",
|
|
272
|
+
path: "/twitter/user/home_timeline",
|
|
273
|
+
description:
|
|
274
|
+
"Get YOUR authenticated account's Home timeline (the 'Following'/'For you' feed), most recent first. Requires an authenticated session behind your key. Returns tweets with author and metrics plus a cursor. Use this to read what your account would see when it opens X.",
|
|
275
|
+
shape: { ...PAGINATION },
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
name: "twitter_bookmarks",
|
|
279
|
+
path: "/twitter/user/bookmarks",
|
|
280
|
+
description:
|
|
281
|
+
"List YOUR authenticated account's bookmarked tweets, most recent first. Requires an authenticated session behind your key. Returns each bookmarked tweet with author and metrics plus a cursor.",
|
|
282
|
+
shape: { ...PAGINATION },
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
name: "twitter_bookmark_search",
|
|
286
|
+
path: "/twitter/user/bookmark_search",
|
|
287
|
+
description:
|
|
288
|
+
"Full-text search within YOUR authenticated account's bookmarks. Requires an authenticated session behind your key. Returns matching bookmarked tweets plus a cursor. Use this to retrieve a previously bookmarked tweet by keyword.",
|
|
289
|
+
shape: {
|
|
290
|
+
query: z.string().describe(
|
|
291
|
+
"Search terms to match against your bookmarked tweets' text.",
|
|
292
|
+
),
|
|
293
|
+
...PAGINATION,
|
|
294
|
+
},
|
|
295
|
+
},
|
|
296
|
+
{
|
|
297
|
+
name: "twitter_dm_list",
|
|
298
|
+
path: "/twitter/dm/list",
|
|
299
|
+
description:
|
|
300
|
+
"List YOUR authenticated account's Direct Message conversations (inbox), each with the participant and a conversation_id you can pass to twitter_dm_conversation. Requires an authenticated session behind your key. Read-only: this does not send DMs.",
|
|
301
|
+
shape: {},
|
|
302
|
+
},
|
|
303
|
+
{
|
|
304
|
+
name: "twitter_dm_conversation",
|
|
305
|
+
path: "/twitter/dm/conversation",
|
|
306
|
+
description:
|
|
307
|
+
"Get the messages in one Direct Message conversation by its conversation_id (from twitter_dm_list). Requires an authenticated session behind your key. Returns each message with sender id, time, and text. Read-only: this does not send DMs.",
|
|
308
|
+
shape: {
|
|
309
|
+
conversation_id: z.string().describe(
|
|
310
|
+
"The conversation_id from a twitter_dm_list entry identifying which DM thread to read.",
|
|
311
|
+
),
|
|
312
|
+
},
|
|
313
|
+
},
|
|
314
|
+
|
|
315
|
+
// ── Writes: tweet authoring ────────────────────────────────────────────────
|
|
316
|
+
{
|
|
317
|
+
name: "twitter_create_tweet",
|
|
318
|
+
path: "/twitter/tweet/create",
|
|
319
|
+
method: "POST",
|
|
320
|
+
write: true,
|
|
321
|
+
description:
|
|
322
|
+
"Post a new tweet AS your authenticated account. Set reply_to to post a reply, or quote to post a quote-tweet. This publishes publicly and is not silently reversible (use twitter_delete_tweet to remove it). Requires an authenticated session with write capability behind your key. Returns the new tweet_id and url.",
|
|
323
|
+
shape: {
|
|
324
|
+
text: z.string().min(1).describe(
|
|
325
|
+
"The tweet body text (1 to 280 characters, or longer if the account has extended limits).",
|
|
326
|
+
),
|
|
327
|
+
reply_to: z.string().optional().describe(
|
|
328
|
+
"Optional. Numeric id of the tweet to reply to. When set, this tweet is posted as a reply in that conversation.",
|
|
329
|
+
),
|
|
330
|
+
quote: z.string().optional().describe(
|
|
331
|
+
"Optional. Numeric id of the tweet to quote. When set, this tweet quote-tweets that tweet.",
|
|
332
|
+
),
|
|
333
|
+
media_ids: z.string().optional().describe(
|
|
334
|
+
"Optional. Comma-separated media id(s) from a prior media upload to attach (images/video).",
|
|
335
|
+
),
|
|
336
|
+
...ACCOUNT,
|
|
337
|
+
},
|
|
338
|
+
},
|
|
339
|
+
{
|
|
340
|
+
name: "twitter_delete_tweet",
|
|
341
|
+
path: "/twitter/tweet/delete",
|
|
342
|
+
method: "POST",
|
|
343
|
+
write: true,
|
|
344
|
+
destructive: true,
|
|
345
|
+
description:
|
|
346
|
+
"Delete a tweet AS your authenticated account. Irreversible: the tweet is permanently removed. You can only delete tweets your authenticated account authored. Provide the tweet id or url. Requires write capability behind your key.",
|
|
347
|
+
shape: { ...TWEET_REF, ...ACCOUNT },
|
|
348
|
+
},
|
|
349
|
+
// ── Writes: engagement (favorite / retweet / bookmark) + inverses ──────────
|
|
350
|
+
{
|
|
351
|
+
name: "twitter_favorite_tweet",
|
|
352
|
+
path: "/twitter/tweet/favorite",
|
|
353
|
+
method: "POST",
|
|
354
|
+
write: true,
|
|
355
|
+
description:
|
|
356
|
+
"Like (favorite) a tweet AS your authenticated account. Provide the tweet id or url. Requires write capability behind your key. Reverse with twitter_unfavorite_tweet.",
|
|
357
|
+
shape: { ...TWEET_REF, ...ACCOUNT },
|
|
358
|
+
},
|
|
359
|
+
{
|
|
360
|
+
name: "twitter_unfavorite_tweet",
|
|
361
|
+
path: "/twitter/tweet/unfavorite",
|
|
362
|
+
method: "POST",
|
|
363
|
+
write: true,
|
|
364
|
+
destructive: true,
|
|
365
|
+
description:
|
|
366
|
+
"Remove a like (unfavorite) from a tweet AS your authenticated account. Provide the tweet id or url. Requires write capability behind your key.",
|
|
367
|
+
shape: { ...TWEET_REF, ...ACCOUNT },
|
|
368
|
+
},
|
|
369
|
+
{
|
|
370
|
+
name: "twitter_retweet",
|
|
371
|
+
path: "/twitter/tweet/retweet",
|
|
372
|
+
method: "POST",
|
|
373
|
+
write: true,
|
|
374
|
+
description:
|
|
375
|
+
"Retweet a tweet AS your authenticated account. Provide the tweet id or url. Requires write capability behind your key. Reverse with twitter_unretweet.",
|
|
376
|
+
shape: { ...TWEET_REF, ...ACCOUNT },
|
|
377
|
+
},
|
|
378
|
+
{
|
|
379
|
+
name: "twitter_unretweet",
|
|
380
|
+
path: "/twitter/tweet/unretweet",
|
|
381
|
+
method: "POST",
|
|
382
|
+
write: true,
|
|
383
|
+
destructive: true,
|
|
384
|
+
description:
|
|
385
|
+
"Undo a retweet AS your authenticated account. Provide the tweet id or url. Requires write capability behind your key.",
|
|
386
|
+
shape: { ...TWEET_REF, ...ACCOUNT },
|
|
387
|
+
},
|
|
388
|
+
{
|
|
389
|
+
name: "twitter_bookmark_tweet",
|
|
390
|
+
path: "/twitter/tweet/bookmark",
|
|
391
|
+
method: "POST",
|
|
392
|
+
write: true,
|
|
393
|
+
description:
|
|
394
|
+
"Bookmark a tweet to YOUR authenticated account's private bookmarks. Provide the tweet id or url. Requires write capability behind your key. Reverse with twitter_unbookmark_tweet.",
|
|
395
|
+
shape: { ...TWEET_REF, ...ACCOUNT },
|
|
396
|
+
},
|
|
397
|
+
{
|
|
398
|
+
name: "twitter_unbookmark_tweet",
|
|
399
|
+
path: "/twitter/tweet/unbookmark",
|
|
400
|
+
method: "POST",
|
|
401
|
+
write: true,
|
|
402
|
+
destructive: true,
|
|
403
|
+
description:
|
|
404
|
+
"Remove a tweet from YOUR authenticated account's bookmarks. Provide the tweet id or url. Requires write capability behind your key.",
|
|
405
|
+
shape: { ...TWEET_REF, ...ACCOUNT },
|
|
406
|
+
},
|
|
407
|
+
// ── Writes: follow graph ───────────────────────────────────────────────────
|
|
408
|
+
{
|
|
409
|
+
name: "twitter_follow_user",
|
|
410
|
+
path: "/twitter/user/follow",
|
|
411
|
+
method: "POST",
|
|
412
|
+
write: true,
|
|
413
|
+
description:
|
|
414
|
+
"Follow a user AS your authenticated account, by numeric user_id. Requires write capability behind your key. Reverse with twitter_unfollow_user.",
|
|
415
|
+
shape: {
|
|
416
|
+
user_id: z.string().describe(
|
|
417
|
+
"Numeric user id of the account to follow. Resolve a handle to a user_id first with twitter_user_info.",
|
|
418
|
+
),
|
|
419
|
+
...ACCOUNT,
|
|
420
|
+
},
|
|
421
|
+
},
|
|
422
|
+
{
|
|
423
|
+
name: "twitter_unfollow_user",
|
|
424
|
+
path: "/twitter/user/unfollow",
|
|
425
|
+
method: "POST",
|
|
426
|
+
write: true,
|
|
427
|
+
destructive: true,
|
|
428
|
+
description:
|
|
429
|
+
"Unfollow a user AS your authenticated account, by numeric user_id. Requires write capability behind your key.",
|
|
430
|
+
shape: {
|
|
431
|
+
user_id: z.string().describe(
|
|
432
|
+
"Numeric user id of the account to unfollow.",
|
|
433
|
+
),
|
|
434
|
+
...ACCOUNT,
|
|
435
|
+
},
|
|
436
|
+
},
|
|
70
437
|
];
|
|
71
438
|
|
|
72
439
|
// Pure query-string builder: drops undefined/null/empty values, URL-encodes the rest.
|