@twitterapis/mcp 0.7.0 → 0.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.7.2 (2026-08-14)
4
+
5
+ ### Added
6
+
7
+ - **3 new compat tools for tweet monitoring** (task #87): `twitter_x_user_stream_add_user`, `twitter_x_user_stream_remove_user`, `twitter_x_user_stream_list_users` -- drop-in equivalents of `twitter_monitor_create`/`twitter_monitor_delete`/`twitter_monitor_list` using an alternate request/response envelope shape, for migrating an existing integration built against that shape without a rewrite. Free per call, same underlying monitor system, same safety checks as the native tools. The catalog is now **74 tools: 45 reads and 29 write actions**.
8
+
9
+ ### Fixed
10
+
11
+ - **The generator's `toolPathFor` only special-cased `/account/*` as un-prefixed** (billing reads mounted at the API root rather than under `/twitter/`); the 3 new compat routes live at `/oapi/x_user_stream/*`, the same shape of gap, now handled identically.
12
+
13
+ ## 0.7.1 (2026-08-14)
14
+
15
+ ### Added
16
+
17
+ - **10 new tools for account monitoring + webhooks** (task #49, backend build plan Phase 5): watch an X account for new posts and get them pushed to your own HTTPS endpoint instead of polling. `twitter_monitor_create`, `twitter_monitor_list`, `twitter_monitor_update`, `twitter_monitor_delete`, `twitter_monitor_health`, `twitter_monitor_deliveries`, `twitter_monitor_webhook_create`, `twitter_monitor_webhook_list`, `twitter_monitor_webhook_delete`, `twitter_monitor_webhook_test`. Free per call (account administration, not a metered Twitter read); needs only your API key, no linked X session. The catalog is now **71 tools: 44 reads and 27 write actions**.
18
+
19
+ ### Fixed
20
+
21
+ - **The generator (`scripts/gen-tools.mjs`) could not represent a DELETE route, or two HTTP methods on the same REST path**, which is exactly the shape the monitor/webhook endpoints need (`/monitor/{id}` is POST to update and DELETE to remove; `/monitor` and `/webhook` are each GET to list and POST to create). The endpoint table was keyed by path alone (a second method on the same path silently overwrote the first) and skipped every method that wasn't `get`/`post` outright, so a vendored `delete` operation never reached the catalog at all. Endpoints are now keyed by `(method, path)`; an override targeting an ambiguous path sets `method: "..."` to say which one. A `{name}` URL-template segment (this API's spec declares no formal `in: "path"` parameter for one) is synthesized as a required arg and threaded through as the tool's `pathParams`, which the runtime (`src/index.js`, via the new `resolvePathParams` in `src/query.js`) substitutes into the URL instead of sending as a query-string or JSON-body field. Regression-tested against a synthetic route table in `test/gen-tools-endpoints.mjs`, independent of the real spec.
22
+
3
23
  ## 0.7.0 (2026-08-11)
4
24
 
5
25
  ### Removed
package/README.md CHANGED
@@ -91,9 +91,9 @@ Restart Claude Desktop. The `twitter_*` tools appear in the tool picker.
91
91
 
92
92
  ## Tools
93
93
 
94
- 61 tools: 40 reads and 21 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`).
94
+ 74 tools: 45 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 13 monitoring tools are also free (account administration, not metered reads).
95
95
 
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 **all 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) are annotated `destructiveHint: true` so MCP clients can prompt before running them.
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
 
98
98
  ### Reads
99
99
 
@@ -166,6 +166,26 @@ Public reads (search, profiles, tweets, followers, likes) work with just your AP
166
166
 
167
167
  See also `twitter_article_get` and `twitter_article_list` above.
168
168
 
169
+ ### Monitoring _(webhook delivery of new posts; free, not metered)_
170
+
171
+ Watch an X account for new posts and get them pushed to your own HTTPS endpoint, HMAC-signed, instead of polling. Register a webhook first, then create a monitor; every new post from a watched handle is delivered to every active webhook on your account (or a restricted subset via `webhook_ids`). Monitor/webhook CRUD is account administration, not a metered Twitter read, so every tool below is free.
172
+
173
+ | Tool | What it does |
174
+ |---|---|
175
+ | `twitter_monitor_create` | Start watching an X account (`handle`) for new posts |
176
+ | `twitter_monitor_list` | List every monitor on your account |
177
+ | `twitter_monitor_update` | Pause/resume a monitor or change its `webhook_ids` restriction |
178
+ | `twitter_monitor_delete` | Stop and remove a monitor (irreversible) |
179
+ | `twitter_monitor_health` | One monitor's status, degradation flag, poll interval, cursor position |
180
+ | `twitter_monitor_deliveries` | Recent delivery events across every monitor, with detection + delivery latency |
181
+ | `twitter_x_user_stream_add_user` | Compat drop-in for `twitter_monitor_create` using an x_user_stream-shaped envelope |
182
+ | `twitter_x_user_stream_remove_user` | Compat drop-in for `twitter_monitor_delete` using an x_user_stream-shaped envelope |
183
+ | `twitter_x_user_stream_list_users` | Compat drop-in for `twitter_monitor_list` using an x_user_stream-shaped envelope |
184
+ | `twitter_monitor_webhook_create` | Register an HTTPS delivery URL; returns the HMAC signing secret **once** |
185
+ | `twitter_monitor_webhook_list` | List every webhook registered on your account |
186
+ | `twitter_monitor_webhook_delete` | Soft-delete a webhook by id (irreversible from the caller's side) |
187
+ | `twitter_monitor_webhook_test` | Send one signed test event to a webhook right now, synchronously |
188
+
169
189
  ### Session setup
170
190
 
171
191
  Link an X account to your key once, so the account-only reads and write actions act as it (or pass per-call `auth_token`/`ct0` instead).
@@ -256,7 +276,7 @@ Calls are billed to your twitterapis.com account. Almost every endpoint is $0.00
256
276
 
257
277
  **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.
258
278
 
259
- **Is it read-only?** No. 40 read tools work with just your API key; 21 write actions (post, like, retweet, follow, DM, media upload, article create/edit/publish/delete) act as a linked X account or per-call inline credentials.
279
+ **Is it read-only?** No. 45 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.
260
280
 
261
281
  **Which clients are supported?** Claude Desktop, Cursor, Windsurf, and VS Code (Copilot agent mode), or any Model Context Protocol client.
262
282
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@twitterapis/mcp",
3
- "version": "0.7.0",
3
+ "version": "0.7.2",
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",
@@ -26,7 +26,7 @@
26
26
  "build": "node scripts/gen-tools.mjs --write",
27
27
  "build:check": "node scripts/gen-tools.mjs --check",
28
28
  "openapi:refresh": "node scripts/openapi-refresh.mjs",
29
- "test": "node scripts/gen-tools.mjs --check && node test/catalog-identity.mjs && node test/tools.test.mjs && node test/smoke.mjs && node test/openapi-parity.mjs && node test/readme-parity.mjs && node test/firewall.mjs && node test/registry-manifests.mjs",
29
+ "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/smoke.mjs && node test/openapi-parity.mjs && node test/readme-parity.mjs && node test/firewall.mjs && node test/registry-manifests.mjs",
30
30
  "prepublishOnly": "npm test && node test/publish-provenance.mjs",
31
31
  "check:openapi-parity": "node test/openapi-parity.mjs",
32
32
  "check:firewall": "node test/firewall.mjs",
package/src/index.js CHANGED
@@ -24,7 +24,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
24
24
  // places and drifted: the package shipped 0.5.0 while the MCP handshake and the
25
25
  // outbound user-agent both still advertised 0.3.0.
26
26
  const VERSION = createRequire(import.meta.url)("../package.json").version;
27
- import { TOOLS, buildQuery } from "./tools.js";
27
+ import { TOOLS, buildQuery, resolvePathParams, MissingPathParamError } from "./tools.js";
28
28
 
29
29
  const API_KEY = process.env.TWITTERAPIS_KEY;
30
30
  const BASE_URL = (
@@ -44,9 +44,26 @@ if (!API_KEY) {
44
44
  // from the query string, so the same buildQuery path serves both and only the
45
45
  // HTTP method differs. A few POST endpoints (customer/session, user_login,
46
46
  // media/upload) instead read a JSON request body; those tools set jsonBody:true
47
- // and callEndpoint sends the args in the body rather than the query string.
48
- async function callEndpoint(path, args, method = "GET", jsonBody = false) {
49
- const all = args || {};
47
+ // and callEndpoint sends the args in the body rather than the query string. A
48
+ // handful of monitoring endpoints (/monitor/{id}, /webhook/{id}, ...) carry a
49
+ // REST path parameter instead: those tools set pathParams (the arg names to
50
+ // substitute into the URL template) and callEndpoint splices them into path
51
+ // before building the query string or body, so a pathParams arg never leaks
52
+ // into either.
53
+ async function callEndpoint(path, args, method = "GET", jsonBody = false, pathParams = []) {
54
+ // Fill {name} URL segments from args and strip those keys, so a pathParams arg
55
+ // (e.g. a monitor/webhook id) never also leaks into the query string or JSON
56
+ // body. A missing value fails loudly rather than shipping a request that still
57
+ // contains the literal "{id}" against the API.
58
+ let resolvedPath, all;
59
+ try {
60
+ ({ path: resolvedPath, args: all } = resolvePathParams(path, pathParams, args));
61
+ } catch (err) {
62
+ if (err instanceof MissingPathParamError) {
63
+ return { isError: true, content: [{ type: "text", text: err.message }] };
64
+ }
65
+ throw err;
66
+ }
50
67
 
51
68
  const headers = {
52
69
  // The API accepts either header; send both for maximum compatibility.
@@ -64,7 +81,7 @@ async function callEndpoint(path, args, method = "GET", jsonBody = false) {
64
81
  // and user_login the credentials ARE the payload the handler reads from the
65
82
  // body, so they must NOT be diverted into x-* headers the way per-call inline
66
83
  // creds are on the query-string tools.
67
- url = `${BASE_URL}${path}`;
84
+ url = `${BASE_URL}${resolvedPath}`;
68
85
  headers["content-type"] = "application/json";
69
86
  reqBody = JSON.stringify(all);
70
87
  } else {
@@ -75,7 +92,7 @@ async function callEndpoint(path, args, method = "GET", jsonBody = false) {
75
92
  // session is used. Lets a single key act as many accounts.
76
93
  const { auth_token, ct0, user_agent, proxy_url, ...rest } = all;
77
94
  const q = buildQuery(rest);
78
- url = `${BASE_URL}${path}${q ? `?${q}` : ""}`;
95
+ url = `${BASE_URL}${resolvedPath}${q ? `?${q}` : ""}`;
79
96
  if (auth_token && ct0) {
80
97
  headers["x-auth-token"] = auth_token;
81
98
  headers["x-ct0"] = ct0;
@@ -138,7 +155,7 @@ for (const tool of TOOLS) {
138
155
  server.registerTool(
139
156
  tool.name,
140
157
  { description: tool.description, inputSchema: tool.shape, annotations },
141
- async (args) => callEndpoint(tool.path, args, method, Boolean(tool.jsonBody)),
158
+ async (args) => callEndpoint(tool.path, args, method, Boolean(tool.jsonBody), tool.pathParams || []),
142
159
  );
143
160
  }
144
161
 
package/src/query.js CHANGED
@@ -14,3 +14,35 @@ export function buildQuery(args) {
14
14
  }
15
15
  return qs.toString();
16
16
  }
17
+
18
+ // Thrown by resolvePathParams when a tool's pathParams arg is missing/empty. The
19
+ // caller decides how to surface it (index.js turns it into an MCP tool error);
20
+ // this module stays a pure mechanical helper with no knowledge of MCP shapes.
21
+ export class MissingPathParamError extends Error {
22
+ constructor(name, path) {
23
+ super(`Missing required path parameter "${name}" for ${path}.`);
24
+ this.name = "MissingPathParamError";
25
+ this.paramName = name;
26
+ }
27
+ }
28
+
29
+ // Substitutes {name} URL-template segments (e.g. "/monitor/{id}") from `args`,
30
+ // for the handful of REST endpoints that carry a path parameter instead of a
31
+ // query/body one (monitor/webhook CRUD by id). Returns the resolved path plus
32
+ // args with every consumed key REMOVED, so a pathParams arg never also leaks
33
+ // into the query string or JSON body downstream. Throws MissingPathParamError
34
+ // rather than silently shipping a request that still contains the literal
35
+ // "{id}" against the API.
36
+ export function resolvePathParams(path, pathParams, args) {
37
+ const rest = { ...(args || {}) };
38
+ let resolved = path;
39
+ for (const name of pathParams || []) {
40
+ const value = rest[name];
41
+ delete rest[name];
42
+ if (value === undefined || value === null || value === "") {
43
+ throw new MissingPathParamError(name, path);
44
+ }
45
+ resolved = resolved.replace(`{${name}}`, encodeURIComponent(String(value)));
46
+ }
47
+ return { path: resolved, args: rest };
48
+ }
package/src/tools.js CHANGED
@@ -8,18 +8,22 @@
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: 61 tools (40 reads, 21 writes).
11
+ // Catalog: 74 tools (45 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, and the 4
17
- // jsonBody tools, whose fields travel in a JSON request body. A tool with
18
- // `method: "POST"` is a write that acts on behalf of the authenticated account
19
- // behind your API key; reads are GET and default when `method` is omitted.
16
+ // inline credentials, which travel as x-* request headers, the 4
17
+ // jsonBody tools, whose fields travel in a JSON request body, and any arg listed
18
+ // in pathParams, which is substituted into the URL path (e.g. {id}) instead. A
19
+ // tool with `method: "POST"` or `method: "DELETE"` is a write that acts on
20
+ // behalf of the authenticated account behind your API key; reads are GET and
21
+ // default when `method` is omitted.
20
22
  //
21
23
  // write:true -> action mutates account/Twitter state (readOnlyHint:false)
22
24
  // destructive:true -> action removes/reverses state (delete, un-follow/like/RT/bookmark)
25
+ // pathParams -> arg names substituted into the URL template, not sent as
26
+ // query-string or body fields (e.g. ["id"] for /monitor/{id})
23
27
  import { z } from "zod";
24
28
 
25
29
  export const TOOLS = [
@@ -1361,8 +1365,173 @@ export const TOOLS = [
1361
1365
  ),
1362
1366
  },
1363
1367
  },
1368
+ {
1369
+ name: "twitter_monitor_create",
1370
+ path: "/twitter/monitor",
1371
+ method: "POST",
1372
+ write: true,
1373
+ description:
1374
+ "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
+ shape: {
1376
+ handle: z.string().min(1).describe(
1377
+ "The X username to watch, without the leading @ (e.g. 'elonmusk').",
1378
+ ),
1379
+ webhook_ids: z.string().optional().describe(
1380
+ "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
+ ),
1382
+ },
1383
+ },
1384
+ {
1385
+ name: "twitter_monitor_list",
1386
+ path: "/twitter/monitor",
1387
+ description:
1388
+ "List every monitor on your account: id, subject (its from:<handle> query), kind, status ('active' or 'paused'), degraded flag, events_possibly_missed, webhook_ids restriction, and created_at. Takes no arguments.",
1389
+ shape: {},
1390
+ },
1391
+ {
1392
+ name: "twitter_monitor_update",
1393
+ path: "/twitter/monitor/{id}",
1394
+ method: "POST",
1395
+ write: true,
1396
+ pathParams: ["id"],
1397
+ 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.",
1399
+ shape: {
1400
+ id: z.string().describe(
1401
+ "The monitor's id, from twitter_monitor_create or twitter_monitor_list.",
1402
+ ),
1403
+ status: z.enum(["active","paused"]).optional().describe(
1404
+ "'paused' to pause the monitor, 'active' to resume it. Omit to leave status unchanged.",
1405
+ ),
1406
+ webhook_ids: z.string().optional().describe(
1407
+ "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
+ ),
1409
+ },
1410
+ },
1411
+ {
1412
+ name: "twitter_monitor_delete",
1413
+ path: "/twitter/monitor/{id}",
1414
+ method: "DELETE",
1415
+ write: true,
1416
+ destructive: true,
1417
+ pathParams: ["id"],
1418
+ description:
1419
+ "Stop and remove a monitor by id. Irreversible: create a new monitor with twitter_monitor_create if you want to watch that handle again. Delivery history referencing this monitor is retained, not cascade-deleted. Free per call.",
1420
+ shape: {
1421
+ id: z.string().describe(
1422
+ "The monitor's id, from twitter_monitor_create or twitter_monitor_list.",
1423
+ ),
1424
+ },
1425
+ },
1426
+ {
1427
+ name: "twitter_monitor_health",
1428
+ path: "/twitter/monitor/{id}/health",
1429
+ pathParams: ["id"],
1430
+ description:
1431
+ "Read one monitor's current status, degradation flag, poll interval, possibly-missed-event count, and cursor position (last_tweet_id, last_poll_at), for building your own health dashboard. Free per call.",
1432
+ shape: {
1433
+ id: z.string().describe(
1434
+ "The monitor's id, from twitter_monitor_create or twitter_monitor_list.",
1435
+ ),
1436
+ },
1437
+ },
1438
+ {
1439
+ name: "twitter_monitor_deliveries",
1440
+ path: "/twitter/monitor/deliveries",
1441
+ description:
1442
+ "List your most recent monitor delivery events across every monitor, most recent first: id, monitor_id, tweet_id, status, tweet_created_at, and the real measured latency (detected_lag_ms, from X's own post timestamp to enqueue; delivery_lag_ms, the separate queue-to-webhook-POST time; total_lag_ms). Free per call.",
1443
+ shape: {
1444
+ limit: z.number().int().min(1).max(200).optional().describe(
1445
+ "Max delivery events to return, 1 to 200. Defaults to 50 when omitted.",
1446
+ ),
1447
+ },
1448
+ },
1449
+ {
1450
+ name: "twitter_x_user_stream_add_user",
1451
+ path: "/oapi/x_user_stream/add_user_to_monitor_tweet",
1452
+ method: "POST",
1453
+ write: true,
1454
+ description:
1455
+ "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.",
1456
+ shape: {
1457
+ x_user_name: z.string().describe(
1458
+ "The X username to watch, without the @.",
1459
+ ),
1460
+ },
1461
+ },
1462
+ {
1463
+ name: "twitter_x_user_stream_remove_user",
1464
+ path: "/oapi/x_user_stream/remove_user_to_monitor_tweet",
1465
+ method: "POST",
1466
+ write: true,
1467
+ destructive: true,
1468
+ description:
1469
+ "Compat drop-in for twitter_monitor_delete using an x_user_stream-shaped envelope: stop watching an account. Irreversible. Free per call.",
1470
+ shape: {
1471
+ id_for_user: z.string().describe(
1472
+ "The monitor id, from twitter_x_user_stream_list_users. Same value as a twitter_monitor_* tool's monitor id.",
1473
+ ),
1474
+ },
1475
+ },
1476
+ {
1477
+ name: "twitter_x_user_stream_list_users",
1478
+ path: "/oapi/x_user_stream/get_user_to_monitor_tweet",
1479
+ description:
1480
+ "Compat drop-in for twitter_monitor_list using an x_user_stream-shaped envelope: list every account you are currently tweet-monitoring. Honest field mapping, not fabricated: x_user_id is always null (this API stores no numeric Twitter user id) and is_monitor_profile is always 0 (profile-change monitoring is not a capability this API has). Free per call.",
1481
+ shape: {},
1482
+ },
1483
+ {
1484
+ name: "twitter_monitor_webhook_create",
1485
+ path: "/twitter/webhook",
1486
+ method: "POST",
1487
+ write: true,
1488
+ description:
1489
+ "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.",
1490
+ shape: {
1491
+ url: z.string().min(1).describe(
1492
+ "Your https delivery endpoint, e.g. 'https://example.com/webhooks/twitterapis'. Private, loopback, link-local, and metadata IPs are refused, re-checked at every delivery, not just at registration.",
1493
+ ),
1494
+ },
1495
+ },
1496
+ {
1497
+ name: "twitter_monitor_webhook_list",
1498
+ path: "/twitter/webhook",
1499
+ description:
1500
+ "List every webhook registered on your account: id, url, status ('active' delivers, 'disabled' means the endpoint returned a 410 Gone and needs re-registering to reactivate), and created_at. The signing secret is never returned here, only at creation. Takes no arguments.",
1501
+ shape: {},
1502
+ },
1503
+ {
1504
+ name: "twitter_monitor_webhook_delete",
1505
+ path: "/twitter/webhook/{id}",
1506
+ method: "DELETE",
1507
+ write: true,
1508
+ destructive: true,
1509
+ pathParams: ["id"],
1510
+ description:
1511
+ "Soft-delete a webhook by id: it stops receiving deliveries immediately and disappears from twitter_monitor_webhook_list, but delivery history referencing it is retained rather than cascade-deleted. Irreversible from the caller's side (register a new webhook with twitter_monitor_webhook_create to resume delivery). Free per call.",
1512
+ shape: {
1513
+ id: z.string().describe(
1514
+ "The webhook's id, from twitter_monitor_webhook_create or twitter_monitor_webhook_list.",
1515
+ ),
1516
+ },
1517
+ },
1518
+ {
1519
+ name: "twitter_monitor_webhook_test",
1520
+ path: "/twitter/webhook/{id}/test",
1521
+ method: "POST",
1522
+ write: true,
1523
+ pathParams: ["id"],
1524
+ description:
1525
+ "Send one HMAC-signed test event to this webhook's URL right now and return the outcome synchronously: delivered (true if your endpoint returned a 2xx within the delivery timeout), status_code, and error. Unlike a real monitor event, a test send is never queued, retried, or dead-lettered, it is a one-shot diagnostic to confirm your endpoint and signature verification both work before relying on the webhook. Free per call.",
1526
+ shape: {
1527
+ id: z.string().describe(
1528
+ "The webhook's id, from twitter_monitor_webhook_create or twitter_monitor_webhook_list.",
1529
+ ),
1530
+ },
1531
+ },
1364
1532
  ];
1365
1533
 
1366
- // The query-string builder is hand-written logic, not catalog data, so it lives
1367
- // in its own module and is re-exported here to keep this file's one import path.
1368
- export { buildQuery } from "./query.js";
1534
+ // The query-string builder and the path-param substitution helper are
1535
+ // hand-written logic, not catalog data, so they live in their own module and
1536
+ // are re-exported here to keep this file's one import path.
1537
+ export { buildQuery, resolvePathParams, MissingPathParamError } from "./query.js";