@datadive-tools/mcp 0.17.0 → 0.18.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/dist/lib.js CHANGED
@@ -139,7 +139,7 @@ function extractMessage(body) {
139
139
  }
140
140
 
141
141
  // package.json
142
- var version = "0.17.0";
142
+ var version = "0.18.0";
143
143
 
144
144
  // src/http/client.ts
145
145
  var PKG_VERSION = version;
@@ -179,6 +179,7 @@ async function httpGet(ctx, path, query) {
179
179
  method: "GET",
180
180
  headers: {
181
181
  ...authHeaders(ctx.config),
182
+ ...forwardedForHeader(ctx.config),
182
183
  accept: "application/json",
183
184
  "user-agent": userAgent(ctx.toolName)
184
185
  }
@@ -196,6 +197,27 @@ async function httpPost(ctx, path, body) {
196
197
  method: "POST",
197
198
  headers: {
198
199
  ...authHeaders(ctx.config),
200
+ ...forwardedForHeader(ctx.config),
201
+ accept: "application/json",
202
+ "content-type": "application/json",
203
+ "user-agent": userAgent(ctx.toolName)
204
+ },
205
+ body: JSON.stringify(body)
206
+ });
207
+ } catch (e) {
208
+ throw networkError(url, e);
209
+ }
210
+ return handleResponse(res);
211
+ }
212
+ async function httpPatch(ctx, path, body) {
213
+ const url = buildUrl(ctx.config.baseUrl, path);
214
+ let res;
215
+ try {
216
+ res = await fetch(url, {
217
+ method: "PATCH",
218
+ headers: {
219
+ ...authHeaders(ctx.config),
220
+ ...forwardedForHeader(ctx.config),
199
221
  accept: "application/json",
200
222
  "content-type": "application/json",
201
223
  "user-agent": userAgent(ctx.toolName)
@@ -215,6 +237,7 @@ async function httpDelete(ctx, path) {
215
237
  method: "DELETE",
216
238
  headers: {
217
239
  ...authHeaders(ctx.config),
240
+ ...forwardedForHeader(ctx.config),
218
241
  accept: "application/json",
219
242
  "user-agent": userAgent(ctx.toolName)
220
243
  }
@@ -260,6 +283,9 @@ function authHeaders(config) {
260
283
  const c = config.credentials;
261
284
  return c.kind === "bearer" ? { authorization: `Bearer ${c.token}` } : { "x-api-key": c.apiKey };
262
285
  }
286
+ function forwardedForHeader(config) {
287
+ return config.forwardedFor ? { "x-forwarded-for": config.forwardedFor } : {};
288
+ }
263
289
  function unwrap(body) {
264
290
  if (!isObject(body)) return body;
265
291
  if (!("data" in body)) return body;
@@ -426,6 +452,10 @@ var KEYWORD_EXCLUSIONS_MAX_PER_CALL = 100;
426
452
  var KEYWORD_EXCLUSIONS_MAX_PER_NICHE = 1e3;
427
453
  var RANK_RADAR_STATES = ["ACTIVE", "PAUSED", "ARCHIVED"];
428
454
  var RANK_RADAR_STATUSES = [...RANK_RADAR_STATES, "ALL"];
455
+ var TAG_TITLE_MAX_LENGTH = 100;
456
+ var TAG_KEYWORD_IDS_MAX = 1e3;
457
+ var TAG_RESERVED_COLOR = "#EB5757";
458
+ var TAG_HEX_COLOR_PATTERN = /^#[0-9A-Fa-f]{6}$/;
429
459
  var PPC_CAMPAIGN_BASE_SORT_BY = [
430
460
  "name",
431
461
  "state",
@@ -561,7 +591,7 @@ async function readAllKeywords(args, ctx) {
561
591
  var getRankRadarDataTool = {
562
592
  name: "get_rank_radar_data",
563
593
  title: "Get Keyword Rankings for a Rank Radar",
564
- description: `Use this to analyze keyword ranking trends over time. Requires startDate and endDate (yyyy-mm-dd), at most ${RANK_RADAR_MAX_DATE_RANGE_DAYS} days apart. Retrieves historical keyword ranking data for the specified Rank Radar within the date range. Without \`currentPage\` and \`pageSize\` it returns every active keyword in one result. To read one page instead \u2014 faster for large Rank Radars \u2014 pass \`currentPage\` and/or \`pageSize\` (max ${RANK_RADAR_KEYWORDS_MAX_PAGE_SIZE}) and continue with \`currentPage\` + 1 while \`hasNext\` is true. The response carries currentPage, pageSize, total, lastPage, hasNext and hasPrev; \`total\` is the Rank Radar's active keyword count. Paused keywords are not included. Each keyword has id, keyword, searchVolume, relevancy, ranks (per-day { date, organicRank, sponsoredRank, impressionRank }), and any highlight annotations. A rank of 101 means the product was not in the top 100 results that day. For PPC metrics use \`get_rank_radar_ppc_data\`; for Search Query Performance use \`get_rank_radar_sqp_data\`. Use after \`list_rank_radars\` to discover a \`rankRadarId\`.`,
594
+ description: `Use this to analyze keyword ranking trends over time. Requires startDate and endDate (yyyy-mm-dd), at most ${RANK_RADAR_MAX_DATE_RANGE_DAYS} days apart. Retrieves historical keyword ranking data for the specified Rank Radar within the date range. Without \`currentPage\` and \`pageSize\` it returns every active keyword in one result. To read one page instead \u2014 faster for large Rank Radars \u2014 pass \`currentPage\` and/or \`pageSize\` (max ${RANK_RADAR_KEYWORDS_MAX_PAGE_SIZE}) and continue with \`currentPage\` + 1 while \`hasNext\` is true. The response carries currentPage, pageSize, total, lastPage, hasNext and hasPrev; \`total\` is the Rank Radar's active keyword count. Paused keywords are not included. Each keyword has id, keyword, searchVolume, relevancy, ranks (per-day { date, organicRank, sponsoredRank, impressionRank }), and \`highlights\` \u2014 the user tags on this keyword. Each \`highlights\` entry is the same object \`list_rank_radar_tags\` returns (id, title, color, description, startAt, endAt, type, krtId, krtKeywordId); its \`id\` is the \`tagId\` the tag-management tools take. A rank of 101 means the product was not in the top 100 results that day. For PPC metrics use \`get_rank_radar_ppc_data\`; for Search Query Performance use \`get_rank_radar_sqp_data\`. Use after \`list_rank_radars\` to discover a \`rankRadarId\`.`,
565
595
  inputSchema: inputSchema8,
566
596
  annotations: { readOnlyHint: true },
567
597
  handler: async (args, ctx) => {
@@ -1165,17 +1195,193 @@ var resumeRankRadarSearchTermsTool = {
1165
1195
  }
1166
1196
  };
1167
1197
 
1168
- // src/tools/generate-listing-copy.ts
1198
+ // src/tools/list-rank-radar-tags.ts
1169
1199
  import { z as z26 } from "zod";
1200
+ var LIST_RANK_RADAR_TAGS_MAX_PAGE_SIZE = 50;
1170
1201
  var inputSchema27 = {
1171
- nicheId: z26.string().min(1).describe("The niche whose keyword data the copy is written from. Get one from `list_niches`."),
1172
- strategy: z26.enum(LISTING_COPY_STRATEGIES).describe(
1202
+ rankRadarId: z26.string().uuid().describe("The Rank Radar UUID whose tags to list (from `list_rank_radars`)."),
1203
+ currentPage: z26.number().int().min(1).optional().describe("Page number, 1-indexed. Defaults to 1."),
1204
+ pageSize: z26.number().int().min(1).max(LIST_RANK_RADAR_TAGS_MAX_PAGE_SIZE).optional().describe(`Items per page (max ${LIST_RANK_RADAR_TAGS_MAX_PAGE_SIZE}). Defaults to 20.`)
1205
+ };
1206
+ var listRankRadarTagsTool = {
1207
+ name: "list_rank_radar_tags",
1208
+ title: "List Tags of a Rank Radar",
1209
+ description: `Use this to find a \`tagId\` before calling \`update_rank_radar_tag\`, \`delete_rank_radar_tag\`, \`add_rank_radar_tag_search_terms\` or \`remove_rank_radar_tag_search_terms\`. Returns the user tags ("highlights") on the specified Rank Radar, ordered by title (case-insensitive, ascending). Each row carries \`id\`, \`title\`, \`color\`, \`description\`, \`startAt\`, \`endAt\`, \`createdAt\`, and \`keywordCount\` \u2014 the number of Rank Radar keywords this tag is applied to. The system "paused" tag (the one the quota pauser writes with a reserved colour) is not listed and cannot be addressed from these tools. Supports pagination metadata (currentPage, pageSize, total, lastPage, hasNext, hasPrev); a page holds at most ${LIST_RANK_RADAR_TAGS_MAX_PAGE_SIZE} tags.`,
1210
+ inputSchema: inputSchema27,
1211
+ annotations: { readOnlyHint: true },
1212
+ handler: async (args, ctx) => {
1213
+ return await httpGet(
1214
+ { config: ctx.config, toolName: "list_rank_radar_tags" },
1215
+ `/v1/niches/rank-radars/${encodeURIComponent(args.rankRadarId)}/tags`,
1216
+ {
1217
+ currentPage: args.currentPage,
1218
+ pageSize: args.pageSize
1219
+ }
1220
+ );
1221
+ }
1222
+ };
1223
+
1224
+ // src/tools/create-rank-radar-tag.ts
1225
+ import { z as z27 } from "zod";
1226
+ var inputSchema28 = {
1227
+ rankRadarId: z27.string().uuid().describe("The Rank Radar UUID to create the tag on (from `list_rank_radars`)."),
1228
+ title: z27.string().min(1).max(TAG_TITLE_MAX_LENGTH).describe(
1229
+ `Display title (1\u2013${TAG_TITLE_MAX_LENGTH} characters, trimmed on write). Must be unique on this Rank Radar, case-insensitive. A clash returns 409 with \`{ error: 'TAG_TITLE_EXISTS', existingTagId }\` \u2014 reuse that id instead of retrying.`
1230
+ ),
1231
+ color: z27.string().regex(TAG_HEX_COLOR_PATTERN, "color must be in #RRGGBB form").optional().describe(
1232
+ `Hex colour in \`#RRGGBB\` form. Defaults to the UI grey when omitted. The reserved colour \`${TAG_RESERVED_COLOR}\` (used by the system "paused" tag) is rejected with 400.`
1233
+ ),
1234
+ description: z27.string().optional().describe("Free-form description, e.g. 'Prime Day run-up'."),
1235
+ startAt: z27.string().optional().describe("ISO 8601 start date, e.g. '2026-07-01'. Required if `endAt` is set; `endAt >= startAt`."),
1236
+ endAt: z27.string().optional().describe("ISO 8601 end date, e.g. '2026-07-14'. Must be greater than or equal to `startAt`."),
1237
+ rankRadarKeywordIds: z27.array(z27.string().uuid()).min(1).max(TAG_KEYWORD_IDS_MAX).optional().describe(
1238
+ `Optional: Rank Radar keyword ids to tag on creation (1\u2013${TAG_KEYWORD_IDS_MAX}). These are the \`id\` values from \`get_rank_radar_data\`; the ids must all belong to this Rank Radar. To attach more later, use \`add_rank_radar_tag_search_terms\`.`
1239
+ )
1240
+ };
1241
+ var createRankRadarTagTool = {
1242
+ name: "create_rank_radar_tag",
1243
+ title: "Create a Rank Radar Tag",
1244
+ description: `Use this to create a new user tag ("highlight") on a Rank Radar \u2014 the way the UI marks a window on the ranking chart. Writing a tag costs nothing and is reversible with \`delete_rank_radar_tag\`, so no confirmation is required. Returns the new tag row (\`id\`, \`title\`, \`color\`, \`description\`, \`startAt\`, \`endAt\`, \`keywordCount\`, \`createdAt\`); the \`id\` is the \`tagId\` the other tag tools take. If the title clashes with an existing tag on this Rank Radar, the API answers 409 \`{ error: 'TAG_TITLE_EXISTS', existingTagId }\` so callers can reuse the existing tag without a follow-up \`list_rank_radar_tags\`. The reserved colour \`${TAG_RESERVED_COLOR}\` is rejected with 400. Pass \`rankRadarKeywordIds\` to tag a batch of keywords in the same call; otherwise create the tag and attach keywords later with \`add_rank_radar_tag_search_terms\`.`,
1245
+ inputSchema: inputSchema28,
1246
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
1247
+ handler: async (args, ctx) => {
1248
+ const body = { title: args.title };
1249
+ if (args.color !== void 0) body.color = args.color;
1250
+ if (args.description !== void 0) body.description = args.description;
1251
+ if (args.startAt !== void 0) body.startAt = args.startAt;
1252
+ if (args.endAt !== void 0) body.endAt = args.endAt;
1253
+ if (args.rankRadarKeywordIds !== void 0) body.rankRadarKeywordIds = args.rankRadarKeywordIds;
1254
+ return await httpPost(
1255
+ { config: ctx.config, toolName: "create_rank_radar_tag" },
1256
+ `/v1/niches/rank-radars/${encodeURIComponent(args.rankRadarId)}/tags`,
1257
+ body
1258
+ );
1259
+ }
1260
+ };
1261
+
1262
+ // src/tools/update-rank-radar-tag.ts
1263
+ import { z as z28 } from "zod";
1264
+ var inputSchema29 = {
1265
+ rankRadarId: z28.string().uuid().describe("The Rank Radar UUID the tag belongs to (from `list_rank_radars`)."),
1266
+ tagId: z28.string().uuid().describe("The tag UUID to update (the `id` from `list_rank_radar_tags` or `create_rank_radar_tag`)."),
1267
+ title: z28.string().min(1).max(TAG_TITLE_MAX_LENGTH).optional().describe(
1268
+ `New title (1\u2013${TAG_TITLE_MAX_LENGTH} characters, trimmed on write). Must be unique on this Rank Radar, case-insensitive. A clash returns 409 with \`{ error: 'TAG_TITLE_EXISTS', existingTagId }\`.`
1269
+ ),
1270
+ color: z28.string().regex(TAG_HEX_COLOR_PATTERN, "color must be in #RRGGBB form").optional().describe(`Hex colour in \`#RRGGBB\` form. The reserved colour \`${TAG_RESERVED_COLOR}\` is rejected with 400.`),
1271
+ description: z28.string().optional().describe("Free-form description. Pass an empty string to clear."),
1272
+ startAt: z28.string().optional().describe("ISO 8601 start date, e.g. '2026-07-01'. `endAt >= startAt`."),
1273
+ endAt: z28.string().optional().describe("ISO 8601 end date, e.g. '2026-07-14'. `endAt >= startAt`.")
1274
+ };
1275
+ var updateRankRadarTagTool = {
1276
+ name: "update_rank_radar_tag",
1277
+ title: "Update a Rank Radar Tag",
1278
+ description: "Use this to change a tag's title, colour, description, or date window. Partial update: only the fields you pass are modified, the rest are left as they are. The system \"paused\" tag (reserved colour, written by the quota pauser) cannot be addressed from here \u2014 the API answers 404. Writing a tag costs nothing and is reversible, so no confirmation is required. Returns the updated tag row. Title clashes return 409 `{ error: 'TAG_TITLE_EXISTS', existingTagId }`; the reserved colour returns 400; and `endAt < startAt` returns 400 (for whichever side the body omits, the comparison uses the stored value).",
1279
+ inputSchema: inputSchema29,
1280
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
1281
+ handler: async (args, ctx) => {
1282
+ const body = {};
1283
+ if (args.title !== void 0) body.title = args.title;
1284
+ if (args.color !== void 0) body.color = args.color;
1285
+ if (args.description !== void 0) body.description = args.description;
1286
+ if (args.startAt !== void 0) body.startAt = args.startAt;
1287
+ if (args.endAt !== void 0) body.endAt = args.endAt;
1288
+ return await httpPatch(
1289
+ { config: ctx.config, toolName: "update_rank_radar_tag" },
1290
+ `/v1/niches/rank-radars/${encodeURIComponent(args.rankRadarId)}/tags/${encodeURIComponent(args.tagId)}`,
1291
+ body
1292
+ );
1293
+ }
1294
+ };
1295
+
1296
+ // src/tools/delete-rank-radar-tag.ts
1297
+ import { z as z29 } from "zod";
1298
+ var inputSchema30 = {
1299
+ rankRadarId: z29.string().uuid().describe("The Rank Radar UUID the tag belongs to (from `list_rank_radars`)."),
1300
+ tagId: z29.string().uuid().describe("The tag UUID to delete (the `id` from `list_rank_radar_tags`)."),
1301
+ confirm: z29.boolean().optional().describe(
1302
+ "Must be true to proceed. Confirm with the user first \u2014 the tag's keyword assignments are removed too and the tag cannot be recovered."
1303
+ )
1304
+ };
1305
+ var deleteRankRadarTagTool = {
1306
+ name: "delete_rank_radar_tag",
1307
+ title: "Delete a Rank Radar Tag",
1308
+ description: 'Use this to permanently remove a tag from a Rank Radar. \u26A0\uFE0F The tag itself is gone and the assignments linking it to each tagged keyword are removed with it; the keywords themselves keep tracking. The deletion cannot be undone from this tool, so set `confirm: true` only after the user approves. The system "paused" tag (reserved colour, written by the quota pauser) cannot be addressed from here \u2014 the API answers 404. To remove just the assignments and keep the tag, use `remove_rank_radar_tag_search_terms` instead.',
1309
+ inputSchema: inputSchema30,
1310
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
1311
+ handler: async (args, ctx) => {
1312
+ const pending = requireConfirmation(
1313
+ args.confirm,
1314
+ ctx,
1315
+ "Deleting this tag removes it from every keyword it was applied to. The tag cannot be recovered \u2014 if the user only wants to clear its keyword list, use `remove_rank_radar_tag_search_terms`."
1316
+ );
1317
+ if (pending) return pending;
1318
+ await httpDelete(
1319
+ { config: ctx.config, toolName: "delete_rank_radar_tag" },
1320
+ `/v1/niches/rank-radars/${encodeURIComponent(args.rankRadarId)}/tags/${encodeURIComponent(args.tagId)}`
1321
+ );
1322
+ return { status: "deleted", rankRadarId: args.rankRadarId, tagId: args.tagId };
1323
+ }
1324
+ };
1325
+
1326
+ // src/tools/add-rank-radar-tag-search-terms.ts
1327
+ import { z as z30 } from "zod";
1328
+ var inputSchema31 = {
1329
+ rankRadarId: z30.string().uuid().describe("The Rank Radar UUID the tag belongs to (from `list_rank_radars`)."),
1330
+ tagId: z30.string().uuid().describe("The tag UUID to attach keywords to (the `id` from `list_rank_radar_tags`)."),
1331
+ rankRadarKeywordIds: z30.array(z30.string().uuid()).min(1).max(TAG_KEYWORD_IDS_MAX).describe(
1332
+ `Rank Radar keyword ids to attach to this tag (1\u2013${TAG_KEYWORD_IDS_MAX} per call, unique). These are the \`id\` values from \`get_rank_radar_data\`; all ids must belong to this Rank Radar. Safe to repeat: keywords already attached are left unchanged.`
1333
+ )
1334
+ };
1335
+ var addRankRadarTagSearchTermsTool = {
1336
+ name: "add_rank_radar_tag_search_terms",
1337
+ title: "Attach Search Terms to a Rank Radar Tag",
1338
+ description: `Use this to tag a batch of keywords on a Rank Radar \u2014 the same relation \`create_rank_radar_tag\` writes when \`rankRadarKeywordIds\` is passed on creation, so this is how you keep extending a tag's keyword set over time. Reversible with \`remove_rank_radar_tag_search_terms\`, so no confirmation is required. Idempotent: already-attached keywords are left unchanged, so re-sending the same ids after a timeout is safe. Up to ${TAG_KEYWORD_IDS_MAX} ids per call \u2014 split larger batches into multiple calls. The system "paused" tag (reserved colour, written by the quota pauser) is not addressable: the API answers 404.`,
1339
+ inputSchema: inputSchema31,
1340
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
1341
+ handler: async (args, ctx) => {
1342
+ return await httpPost(
1343
+ { config: ctx.config, toolName: "add_rank_radar_tag_search_terms" },
1344
+ `/v1/niches/rank-radars/${encodeURIComponent(args.rankRadarId)}/tags/${encodeURIComponent(args.tagId)}/search-terms`,
1345
+ { rankRadarKeywordIds: args.rankRadarKeywordIds }
1346
+ );
1347
+ }
1348
+ };
1349
+
1350
+ // src/tools/remove-rank-radar-tag-search-terms.ts
1351
+ import { z as z31 } from "zod";
1352
+ var inputSchema32 = {
1353
+ rankRadarId: z31.string().uuid().describe("The Rank Radar UUID the tag belongs to (from `list_rank_radars`)."),
1354
+ tagId: z31.string().uuid().describe("The tag UUID to detach keywords from (the `id` from `list_rank_radar_tags`)."),
1355
+ rankRadarKeywordIds: z31.array(z31.string().uuid()).min(1).max(TAG_KEYWORD_IDS_MAX).describe(
1356
+ `Rank Radar keyword ids to detach from this tag (1\u2013${TAG_KEYWORD_IDS_MAX} per call, unique). These are the \`id\` values from \`get_rank_radar_data\`. Safe to repeat: keywords that are not currently attached are left unchanged.`
1357
+ )
1358
+ };
1359
+ var removeRankRadarTagSearchTermsTool = {
1360
+ name: "remove_rank_radar_tag_search_terms",
1361
+ title: "Detach Search Terms from a Rank Radar Tag",
1362
+ description: `Use this to remove keywords from a tag without deleting the tag itself. The tag keeps its title, colour and date window; only the keyword assignments listed are cleared. Reversible with \`add_rank_radar_tag_search_terms\`, so no confirmation is required. Idempotent: ids that are not currently attached are left unchanged, so re-sending the same ids after a timeout is safe. Up to ${TAG_KEYWORD_IDS_MAX} ids per call \u2014 split larger batches into multiple calls. To remove the tag itself, use \`delete_rank_radar_tag\`. The system "paused" tag (reserved colour, written by the quota pauser) is not addressable: the API answers 404.`,
1363
+ inputSchema: inputSchema32,
1364
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
1365
+ handler: async (args, ctx) => {
1366
+ return await httpPost(
1367
+ { config: ctx.config, toolName: "remove_rank_radar_tag_search_terms" },
1368
+ `/v1/niches/rank-radars/${encodeURIComponent(args.rankRadarId)}/tags/${encodeURIComponent(args.tagId)}/search-terms/remove`,
1369
+ { rankRadarKeywordIds: args.rankRadarKeywordIds }
1370
+ );
1371
+ }
1372
+ };
1373
+
1374
+ // src/tools/generate-listing-copy.ts
1375
+ import { z as z32 } from "zod";
1376
+ var inputSchema33 = {
1377
+ nicheId: z32.string().min(1).describe("The niche whose keyword data the copy is written from. Get one from `list_niches`."),
1378
+ strategy: z32.enum(LISTING_COPY_STRATEGIES).describe(
1173
1379
  "Which generation strategy to use. `cosmo` targets Amazon's COSMO relevance model, `ranking-juice` optimises for DataDive's ranking-juice score, `nlp` writes for keyword coverage, and `cosmo-rufus` targets COSMO plus the Rufus shopping assistant. Ask the user if they have no preference."
1174
1380
  ),
1175
- currentListing: z26.record(z26.union([z26.string(), z26.array(z26.string())])).describe(
1381
+ currentListing: z32.record(z32.union([z32.string(), z32.array(z32.string())])).describe(
1176
1382
  'The seller\'s existing listing to rewrite, as an object \u2014 e.g. `{ "title": "...", "description": "...", "bullets": ["...", "..."] }`. Pass what the user has; it is the starting point the generated copy builds on.'
1177
1383
  ),
1178
- confirm: z26.boolean().optional().describe(
1384
+ confirm: z32.boolean().optional().describe(
1179
1385
  "Must be true to proceed \u2014 each generation spends one AI Copywriter prompt from the quota. Confirm the cost with the user first."
1180
1386
  )
1181
1387
  };
@@ -1183,7 +1389,7 @@ var generateListingCopyTool = {
1183
1389
  name: "generate_listing_copy",
1184
1390
  title: "Generate Listing Copy for a Niche",
1185
1391
  description: "Use this to draft an optimised Amazon listing \u2014 title, bullets and description \u2014 from a niche's keyword research and the seller's current listing. It writes text only: nothing is published to Amazon, and the user still has to paste the result into Seller Central. \u26A0\uFE0F Spends one AI Copywriter prompt from the quota per call and cannot be undone \u2014 set `confirm: true` only after the user approves. Not safe to retry: each call spends another prompt, so if a call errors or times out, poll `get_listing_copy_generation_status` instead of re-calling. Runs asynchronously: returns a `generationId` \u2014 poll `get_listing_copy_generation_status` with it until the status is `complete`.",
1186
- inputSchema: inputSchema27,
1392
+ inputSchema: inputSchema33,
1187
1393
  annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
1188
1394
  handler: async (args, ctx) => {
1189
1395
  const pending = requireConfirmation(
@@ -1201,16 +1407,16 @@ var generateListingCopyTool = {
1201
1407
  };
1202
1408
 
1203
1409
  // src/tools/get-listing-copy-generation-status.ts
1204
- import { z as z27 } from "zod";
1205
- var inputSchema28 = {
1206
- nicheId: z27.string().min(1).describe("The niche the generation was started for."),
1207
- generationId: z27.string().min(1).describe("The `generationId` returned by `generate_listing_copy`.")
1410
+ import { z as z33 } from "zod";
1411
+ var inputSchema34 = {
1412
+ nicheId: z33.string().min(1).describe("The niche the generation was started for."),
1413
+ generationId: z33.string().min(1).describe("The `generationId` returned by `generate_listing_copy`.")
1208
1414
  };
1209
1415
  var getListingCopyGenerationStatusTool = {
1210
1416
  name: "get_listing_copy_generation_status",
1211
1417
  title: "Get Listing Copy Generation Status",
1212
1418
  description: "Use this to poll a listing-copy draft started with `generate_listing_copy` until it finishes. Returns one of three shapes keyed by `status`: `generating` (still running \u2014 poll again in a few seconds), `complete` (with `result`, carrying the generated `title`, `bullets`, `description`, `itemHighlights` and its `rankingJuice` score), or `failed` (with an `error` message). Polling is free \u2014 it does not spend another AI Copywriter prompt, so always poll rather than re-calling `generate_listing_copy`.",
1213
- inputSchema: inputSchema28,
1419
+ inputSchema: inputSchema34,
1214
1420
  annotations: { readOnlyHint: true },
1215
1421
  handler: async (args, ctx) => {
1216
1422
  return await httpGet(
@@ -1221,16 +1427,16 @@ var getListingCopyGenerationStatusTool = {
1221
1427
  };
1222
1428
 
1223
1429
  // src/tools/list-seller-profiles.ts
1224
- import { z as z28 } from "zod";
1225
- var inputSchema29 = {
1226
- currentPage: z28.number().int().min(1).optional().describe("Page number, 1-indexed. Defaults to 1."),
1227
- pageSize: z28.number().int().min(1).max(50).optional().describe("Items per page (max 50). Defaults to 20.")
1430
+ import { z as z34 } from "zod";
1431
+ var inputSchema35 = {
1432
+ currentPage: z34.number().int().min(1).optional().describe("Page number, 1-indexed. Defaults to 1."),
1433
+ pageSize: z34.number().int().min(1).max(50).optional().describe("Items per page (max 50). Defaults to 20.")
1228
1434
  };
1229
1435
  var listSellerProfilesTool = {
1230
1436
  name: "list_seller_profiles",
1231
1437
  title: "List Connected Amazon Seller Profiles",
1232
1438
  description: 'Use this when the user asks which Amazon seller accounts are connected, or as the discovery step to find the `sellerId` and `marketplace` required by `get_asin_inventory_distribution`, `get_seller_catalog`, `get_seller_listing_changes`, and the alert tools. Returns a paginated list of the organization\'s connected seller profiles \u2014 each item has sellerId, sellerName, marketplace (e.g. "com", "co.uk"), hasAdApi (whether Advertising API credentials are connected), and createdAt \u2014 plus pagination metadata (currentPage, pageSize, total, lastPage, hasNext, hasPrev).',
1233
- inputSchema: inputSchema29,
1439
+ inputSchema: inputSchema35,
1234
1440
  annotations: { readOnlyHint: true },
1235
1441
  handler: async (args, ctx) => {
1236
1442
  return await httpGet(
@@ -1245,15 +1451,15 @@ var listSellerProfilesTool = {
1245
1451
  };
1246
1452
 
1247
1453
  // src/tools/get-seller-catalog.ts
1248
- import { z as z30 } from "zod";
1454
+ import { z as z36 } from "zod";
1249
1455
 
1250
1456
  // src/tools/seller-scoped.ts
1251
- import { z as z29 } from "zod";
1457
+ import { z as z35 } from "zod";
1252
1458
  var sellerScopedInputSchema = {
1253
- sellerId: z29.string().min(1).describe(
1459
+ sellerId: z35.string().min(1).describe(
1254
1460
  "Amazon seller account ID. Get it from `list_seller_profiles`, or find it on the Connections page at https://2.datadive.tools."
1255
1461
  ),
1256
- marketplace: z29.enum(SUPPORTED_MARKETPLACES).describe('Amazon marketplace code, e.g. "com" for amazon.com or "co.uk" for amazon.co.uk.')
1462
+ marketplace: z35.enum(SUPPORTED_MARKETPLACES).describe('Amazon marketplace code, e.g. "com" for amazon.com or "co.uk" for amazon.co.uk.')
1257
1463
  };
1258
1464
  function sellerMarketplacePath(sellerId, marketplace, suffix) {
1259
1465
  return `/v1/seller_profiles/${encodeURIComponent(sellerId)}/marketplaces/${encodeURIComponent(
@@ -1262,19 +1468,19 @@ function sellerMarketplacePath(sellerId, marketplace, suffix) {
1262
1468
  }
1263
1469
 
1264
1470
  // src/tools/get-seller-catalog.ts
1265
- var inputSchema30 = {
1471
+ var inputSchema36 = {
1266
1472
  ...sellerScopedInputSchema,
1267
- search: z30.string().min(1).optional().describe("Case-insensitive partial match on product title or brand."),
1268
- brand: z30.string().min(1).optional().describe("Case-insensitive partial match on brand name."),
1269
- status: z30.enum(CATALOG_STATUSES).optional().describe('Listing status filter. "Active" (default) or "all".'),
1270
- currentPage: z30.number().int().min(1).optional().describe("Page number, 1-indexed. Defaults to 1."),
1271
- pageSize: z30.number().int().min(1).max(50).optional().describe("Items per page (max 50). Defaults to 20.")
1473
+ search: z36.string().min(1).optional().describe("Case-insensitive partial match on product title or brand."),
1474
+ brand: z36.string().min(1).optional().describe("Case-insensitive partial match on brand name."),
1475
+ status: z36.enum(CATALOG_STATUSES).optional().describe('Listing status filter. "Active" (default) or "all".'),
1476
+ currentPage: z36.number().int().min(1).optional().describe("Page number, 1-indexed. Defaults to 1."),
1477
+ pageSize: z36.number().int().min(1).max(50).optional().describe("Items per page (max 50). Defaults to 20.")
1272
1478
  };
1273
1479
  var getSellerCatalogTool = {
1274
1480
  name: "get_seller_catalog",
1275
1481
  title: "List Catalog ASINs for a Seller",
1276
1482
  description: "Use this when the user wants to browse or search a seller's own Amazon catalog \u2014 the products they sell on a given marketplace. Requires a `sellerId` + `marketplace` (use `list_seller_profiles` to discover them). Filter by `search` (title/brand), `brand`, and `status` (Active by default, or all). Returns a paginated list where each item has asin, title, parentAsin, brand, status, imageUrl, and hasVariations, plus pagination metadata (currentPage, pageSize, total, lastPage, hasNext, hasPrev).",
1277
- inputSchema: inputSchema30,
1483
+ inputSchema: inputSchema36,
1278
1484
  annotations: { readOnlyHint: true },
1279
1485
  handler: async (args, ctx) => {
1280
1486
  return await httpGet(
@@ -1292,28 +1498,28 @@ var getSellerCatalogTool = {
1292
1498
  };
1293
1499
 
1294
1500
  // src/tools/get-seller-listing-changes.ts
1295
- import { z as z31 } from "zod";
1296
- var inputSchema31 = {
1501
+ import { z as z37 } from "zod";
1502
+ var inputSchema37 = {
1297
1503
  ...sellerScopedInputSchema,
1298
- types: z31.array(z31.enum(LISTING_CHANGE_TYPES)).min(1).optional().describe('Filter to specific change types. Omit for all. Any of "Price", "Content", "Image".'),
1299
- asin: z31.string().min(1).optional().describe("Filter to an exact ASIN or parent ASIN."),
1300
- brand: z31.string().min(1).optional().describe("Case-insensitive partial match on brand name."),
1301
- search: z31.string().min(1).optional().describe("Case-insensitive partial match on product title or brand."),
1302
- startDate: z31.string().regex(ISO_8601, "startDate must be an ISO-8601 date or timestamp (e.g. 2026-05-01 or 2026-05-01T00:00:00Z)").optional().describe("ISO-8601 date/timestamp; return only changes detected on or after this time."),
1303
- endDate: z31.string().regex(ISO_8601, "endDate must be an ISO-8601 date or timestamp (e.g. 2026-05-31 or 2026-05-31T23:59:59Z)").optional().describe("ISO-8601 date/timestamp; return only changes detected on or before this time."),
1304
- sortBy: z31.enum(LISTING_CHANGE_SORT_BY).optional().describe('Sort field. "date" (default) or "type".'),
1305
- sortOrder: z31.enum(SORT_ORDER).optional().describe('Sort direction. "DESC" (default) or "ASC".'),
1306
- includeCorrelations: z31.boolean().optional().describe(
1504
+ types: z37.array(z37.enum(LISTING_CHANGE_TYPES)).min(1).optional().describe('Filter to specific change types. Omit for all. Any of "Price", "Content", "Image".'),
1505
+ asin: z37.string().min(1).optional().describe("Filter to an exact ASIN or parent ASIN."),
1506
+ brand: z37.string().min(1).optional().describe("Case-insensitive partial match on brand name."),
1507
+ search: z37.string().min(1).optional().describe("Case-insensitive partial match on product title or brand."),
1508
+ startDate: z37.string().regex(ISO_8601, "startDate must be an ISO-8601 date or timestamp (e.g. 2026-05-01 or 2026-05-01T00:00:00Z)").optional().describe("ISO-8601 date/timestamp; return only changes detected on or after this time."),
1509
+ endDate: z37.string().regex(ISO_8601, "endDate must be an ISO-8601 date or timestamp (e.g. 2026-05-31 or 2026-05-31T23:59:59Z)").optional().describe("ISO-8601 date/timestamp; return only changes detected on or before this time."),
1510
+ sortBy: z37.enum(LISTING_CHANGE_SORT_BY).optional().describe('Sort field. "date" (default) or "type".'),
1511
+ sortOrder: z37.enum(SORT_ORDER).optional().describe('Sort direction. "DESC" (default) or "ASC".'),
1512
+ includeCorrelations: z37.boolean().optional().describe(
1307
1513
  "When true, include the ranking/conversion `correlation` object per change (CVR and search-term movement before vs after). Defaults to false."
1308
1514
  ),
1309
- currentPage: z31.number().int().min(1).optional().describe("Page number, 1-indexed. Defaults to 1."),
1310
- pageSize: z31.number().int().min(1).max(50).optional().describe("Items per page (max 50). Defaults to 20.")
1515
+ currentPage: z37.number().int().min(1).optional().describe("Page number, 1-indexed. Defaults to 1."),
1516
+ pageSize: z37.number().int().min(1).max(50).optional().describe("Items per page (max 50). Defaults to 20.")
1311
1517
  };
1312
1518
  var getSellerListingChangesTool = {
1313
1519
  name: "get_seller_listing_changes",
1314
1520
  title: "List Listing Changes for a Seller",
1315
1521
  description: "Use this when the user asks what changed on their Amazon listings \u2014 price, content (title/bullets/description), or image edits \u2014 for a seller account. Requires a `sellerId` + `marketplace` (use `list_seller_profiles` to discover them). Filter by `types`, `asin`/parent ASIN, `brand`, `search`, and a `startDate`/`endDate` range; sort with `sortBy`/`sortOrder`. Set `includeCorrelations: true` to attach the ranking/conversion impact per change. Returns a paginated list where each item has asin, title, imageUrl, date, type, contentType, description, previousValue, newValue, and (optionally) correlation, plus pagination metadata (currentPage, pageSize, total, lastPage, hasNext, hasPrev).",
1316
- inputSchema: inputSchema31,
1522
+ inputSchema: inputSchema37,
1317
1523
  annotations: { readOnlyHint: true },
1318
1524
  handler: async (args, ctx) => {
1319
1525
  return await httpGet(
@@ -1338,40 +1544,40 @@ var getSellerListingChangesTool = {
1338
1544
  };
1339
1545
 
1340
1546
  // src/tools/list-ppc-campaigns.ts
1341
- import { z as z32 } from "zod";
1547
+ import { z as z38 } from "zod";
1342
1548
  var PPC_CAMPAIGNS_MAX_WINDOW_DAYS = 90;
1343
1549
  var PPC_CAMPAIGNS_MAX_PAGE_SIZE = 50;
1344
- var inputSchema32 = {
1550
+ var inputSchema38 = {
1345
1551
  ...sellerScopedInputSchema,
1346
- startDate: z32.string().regex(
1552
+ startDate: z38.string().regex(
1347
1553
  ISO_8601,
1348
1554
  "startDate must be an ISO-8601 date or timestamp (e.g. 2026-05-01 or 2026-05-01T00:00:00Z)"
1349
1555
  ).optional().describe("Start of the reporting window, ISO-8601. Defaults to 30 days before endDate."),
1350
- endDate: z32.string().regex(
1556
+ endDate: z38.string().regex(
1351
1557
  ISO_8601,
1352
1558
  "endDate must be an ISO-8601 date or timestamp (e.g. 2026-05-31 or 2026-05-31T23:59:59Z)"
1353
1559
  ).optional().describe(
1354
1560
  `End of the reporting window, ISO-8601. Defaults to now. At most ${PPC_CAMPAIGNS_MAX_WINDOW_DAYS} days after startDate.`
1355
1561
  ),
1356
- asin: z32.string().min(1).optional().describe("Only campaigns advertising this ASIN. Cannot be combined with parentAsin."),
1357
- parentAsin: z32.string().min(1).optional().describe("Only campaigns advertising any ASIN of this variation family. Cannot be combined with asin."),
1358
- search: z32.string().min(3).max(200).optional().describe("Partial match on campaign name or advertised ASIN. At least 3 characters."),
1359
- includePlacements: z32.boolean().optional().describe(
1562
+ asin: z38.string().min(1).optional().describe("Only campaigns advertising this ASIN. Cannot be combined with parentAsin."),
1563
+ parentAsin: z38.string().min(1).optional().describe("Only campaigns advertising any ASIN of this variation family. Cannot be combined with asin."),
1564
+ search: z38.string().min(3).max(200).optional().describe("Partial match on campaign name or advertised ASIN. At least 3 characters."),
1565
+ includePlacements: z38.boolean().optional().describe(
1360
1566
  "Include the per-placement breakdown (topOfSearch, restOfSearch, productPage, offAmazon, each with its bid adjustment). Defaults to true; set false for a smaller response when placements are not needed."
1361
1567
  ),
1362
- sortBy: z32.enum([...PPC_CAMPAIGN_BASE_SORT_BY, ...PPC_CAMPAIGN_PLACEMENT_SORT_BY]).optional().describe(
1568
+ sortBy: z38.enum([...PPC_CAMPAIGN_BASE_SORT_BY, ...PPC_CAMPAIGN_PLACEMENT_SORT_BY]).optional().describe(
1363
1569
  `Sort field. Defaults to "name". Any of ${PPC_CAMPAIGN_BASE_SORT_BY.join(", ")}. The placement fields (e.g. "tosSpend", "tosAcos", "ppBidAdjustment") are only accepted when includePlacements is not false.`
1364
1570
  ),
1365
- sortOrder: z32.enum(SORT_ORDER).optional().describe('Sort direction. "ASC" (default) or "DESC".'),
1366
- state: z32.enum(PPC_CAMPAIGN_STATES).optional().describe("Only ENABLED or only PAUSED campaigns. Omit for both."),
1367
- currentPage: z32.number().int().min(1).optional().describe("Page number, 1-indexed. Defaults to 1."),
1368
- pageSize: z32.number().int().min(1).max(PPC_CAMPAIGNS_MAX_PAGE_SIZE).optional().describe(`Campaigns per page (max ${PPC_CAMPAIGNS_MAX_PAGE_SIZE}). Defaults to 20.`)
1571
+ sortOrder: z38.enum(SORT_ORDER).optional().describe('Sort direction. "ASC" (default) or "DESC".'),
1572
+ state: z38.enum(PPC_CAMPAIGN_STATES).optional().describe("Only ENABLED or only PAUSED campaigns. Omit for both."),
1573
+ currentPage: z38.number().int().min(1).optional().describe("Page number, 1-indexed. Defaults to 1."),
1574
+ pageSize: z38.number().int().min(1).max(PPC_CAMPAIGNS_MAX_PAGE_SIZE).optional().describe(`Campaigns per page (max ${PPC_CAMPAIGNS_MAX_PAGE_SIZE}). Defaults to 20.`)
1369
1575
  };
1370
1576
  var listPpcCampaignsTool = {
1371
1577
  name: "list_ppc_campaigns",
1372
1578
  title: "List PPC Campaigns for a Seller",
1373
1579
  description: `Use this when the user asks about their Sponsored Products campaigns \u2014 which campaigns spend the most, which have a high ACOS, how placements (top of search, rest of search, product pages) perform, or which campaigns advertise a given product. Requires a \`sellerId\` + \`marketplace\` (use \`list_seller_profiles\` to discover them). Narrow to one product with \`asin\`, or to a variation family with \`parentAsin\` (not both). The reporting window defaults to the last 30 days and cannot exceed ${PPC_CAMPAIGNS_MAX_WINDOW_DAYS} days. Returns a paginated list where each campaign has campaignId, name, type, state, targetingType, bidStrategy, budget { type, amount, currencyCode }, window totals (impressions, clicks, ctr, cpc, cvr, spend, unitsSold, orders, sales, tosImpressionShare, acos, roas, tacos), asinCount and, unless includePlacements is false, \`placements\`; plus pagination metadata (currentPage, pageSize, total, lastPage, hasNext, hasPrev). To find the biggest spenders, sort with \`sortBy: "spend", sortOrder: "DESC"\` rather than paging through every campaign. Each call counts toward API usage and is rate limited (about 60 requests/minute). For per-keyword ad data of a Rank Radar, use \`get_rank_radar_ppc_data\`.`,
1374
- inputSchema: inputSchema32,
1580
+ inputSchema: inputSchema38,
1375
1581
  annotations: { readOnlyHint: true },
1376
1582
  handler: async (args, ctx) => {
1377
1583
  if (args.asin && args.parentAsin) {
@@ -1402,21 +1608,21 @@ var listPpcCampaignsTool = {
1402
1608
  };
1403
1609
 
1404
1610
  // src/tools/get-asin-inventory-distribution.ts
1405
- import { z as z33 } from "zod";
1406
- var inputSchema33 = {
1407
- sellerId: z33.string().min(1).describe(
1611
+ import { z as z39 } from "zod";
1612
+ var inputSchema39 = {
1613
+ sellerId: z39.string().min(1).describe(
1408
1614
  "Amazon seller account ID as connected to DataDive. Get it from `list_seller_profiles`, or find it on the Connections page at https://2.datadive.tools."
1409
1615
  ),
1410
- marketplace: z33.enum(SUPPORTED_MARKETPLACES).describe('Amazon marketplace code, e.g. "com" for amazon.com or "co.uk" for amazon.co.uk.'),
1411
- asin: z33.string().regex(/^[A-Z0-9]{10}$/, "ASIN must be 10 uppercase alphanumeric characters (e.g. B08XYZ1234).").describe(
1616
+ marketplace: z39.enum(SUPPORTED_MARKETPLACES).describe('Amazon marketplace code, e.g. "com" for amazon.com or "co.uk" for amazon.co.uk.'),
1617
+ asin: z39.string().regex(/^[A-Z0-9]{10}$/, "ASIN must be 10 uppercase alphanumeric characters (e.g. B08XYZ1234).").describe(
1412
1618
  "Amazon Standard Identification Number (10 uppercase alphanumeric characters, e.g. B08XYZ1234)."
1413
1619
  )
1414
1620
  };
1415
1621
  var getAsinInventoryDistributionTool = {
1416
1622
  name: "get_asin_inventory_distribution",
1417
- title: "Get Per-Fulfillment-Center Inventory Distribution for an ASIN",
1418
- description: "Use this when the user asks about inventory levels, stock by fulfillment center, or where their units are sitting across Amazon's fulfillment network for a specific ASIN. Requires the Amazon sellerId (from `list_seller_profiles` or the user's DataDive Connections page at https://2.datadive.tools), the marketplace code, and the ASIN. Returns totalSellableUnits and a per-FC `distribution` array (fc, state, availableStock, availableStockPercentage). `lastUpdatedAt` may be null when no successful inventory ingestion has occurred in the last 30 days.",
1419
- inputSchema: inputSchema33,
1623
+ title: "Get Inventory Metrics and Fulfillment-Center Distribution for an ASIN",
1624
+ description: "Use this when the user asks about an ASIN's inventory: stock levels, days of stock or when it will run out, low-inventory or aged-inventory risk, low-inventory-level (LILF) or long-term storage (LTSF) fees, or where their units sit across Amazon's fulfillment network. Requires the Amazon sellerId (from `list_seller_profiles` or the user's DataDive Connections page at https://2.datadive.tools), the marketplace code, and the ASIN. Returns the metrics the DataDive Inventory page shows \u2014 fulfillableStock, totalStock, reservedStock, inboundStock, researchingStock, unfulfillableStock, daysOfStock (with daysOfStockSource AMAZON or ESTIMATE), daysOfStock30, daysOfStock90, inLowInventoryState, estWeeklyLilf, estLtsfNextAssessment, ltsfLastMonthActual, agedUnits, oldestUnitAgeDays, with fees in `currency` \u2014 plus totalSellableUnits and a per-FC `distribution` array (fc, state, availableStock, availableStockPercentage). A metric is null, never 0, when Amazon has no data (e.g. merchant-fulfilled); metrics the user's plan does not include are null and listed in `lockedFields` (say they need a plan upgrade, do not report them as zero). `lastUpdatedAt` dates the stock and metrics; `distributionLastUpdatedAt` dates the distribution, which is usually up to 2 days behind. Either may be null when no successful refresh happened in the last 30 days.",
1625
+ inputSchema: inputSchema39,
1420
1626
  annotations: { readOnlyHint: true },
1421
1627
  handler: async (args, ctx) => {
1422
1628
  return await httpGet(
@@ -1429,18 +1635,18 @@ var getAsinInventoryDistributionTool = {
1429
1635
  };
1430
1636
 
1431
1637
  // src/tools/alert-query.ts
1432
- import { z as z34 } from "zod";
1638
+ import { z as z40 } from "zod";
1433
1639
  var alertsQueryInputSchema = {
1434
- sellerId: z34.string().min(1).max(50).optional().describe('Filter to a single connected Amazon seller account ID, e.g. "A1B2C3D4E5".'),
1435
- marketplace: z34.enum(SUPPORTED_MARKETPLACES).optional().describe('Filter to a single Amazon marketplace code, e.g. "com" for amazon.com or "co.uk" for amazon.co.uk.'),
1436
- status: z34.enum(ALERT_STATUSES).optional().describe(
1640
+ sellerId: z40.string().min(1).max(50).optional().describe('Filter to a single connected Amazon seller account ID, e.g. "A1B2C3D4E5".'),
1641
+ marketplace: z40.enum(SUPPORTED_MARKETPLACES).optional().describe('Filter to a single Amazon marketplace code, e.g. "com" for amazon.com or "co.uk" for amazon.co.uk.'),
1642
+ status: z40.enum(ALERT_STATUSES).optional().describe(
1437
1643
  "Lifecycle filter. `active` (default) returns unresolved alerts, `resolved` returns resolved alerts, `all` returns both. Dismissed alerts are never returned."
1438
1644
  ),
1439
- updatedSince: z34.string().regex(ISO_8601, "updatedSince must be an ISO-8601 date or timestamp (e.g. 2026-05-01 or 2026-05-01T00:00:00Z)").optional().describe(
1645
+ updatedSince: z40.string().regex(ISO_8601, "updatedSince must be an ISO-8601 date or timestamp (e.g. 2026-05-01 or 2026-05-01T00:00:00Z)").optional().describe(
1440
1646
  "ISO-8601 timestamp; return only alerts surfaced at or after this time (filters on `lastAlertedAt`). Use it to incrementally sync since your last poll. Defaults to the last 30 days when omitted."
1441
1647
  ),
1442
- currentPage: z34.number().int().min(1).optional().describe("Page number, 1-indexed. Defaults to 1."),
1443
- pageSize: z34.number().int().min(1).max(50).optional().describe("Items per page (max 50). Defaults to 20.")
1648
+ currentPage: z40.number().int().min(1).optional().describe("Page number, 1-indexed. Defaults to 1."),
1649
+ pageSize: z40.number().int().min(1).max(50).optional().describe("Items per page (max 50). Defaults to 20.")
1444
1650
  };
1445
1651
 
1446
1652
  // src/tools/list-indexing-issue-alerts.ts
@@ -1490,12 +1696,12 @@ var listBlindSpendAlertsTool = {
1490
1696
  };
1491
1697
 
1492
1698
  // src/tools/get-quota.ts
1493
- var inputSchema34 = {};
1699
+ var inputSchema40 = {};
1494
1700
  var getQuotaTool = {
1495
1701
  name: "get_quota",
1496
1702
  title: "Get Quota Usage",
1497
1703
  description: "Use this when the user asks how much quota or how many tokens they have left, or before creating a Niche Dive or Rank Radar to check remaining headroom. Returns current quota usage and capacity for each billable feature \u2014 DIVED_ASINS (Dive tokens), RANK_RADAR_KEYWORDS (tracked keywords), PRODUCT_BRIEF_ASINS, and AI_COPYWRITER_PROMPTS \u2014 each with `used` and `capacity` (null when unlimited / not applicable), plus `nextRefreshDate` (ISO-8601 timestamp of the next quota reset, or null). Takes no arguments.",
1498
- inputSchema: inputSchema34,
1704
+ inputSchema: inputSchema40,
1499
1705
  annotations: { readOnlyHint: true },
1500
1706
  handler: async (_args, ctx) => {
1501
1707
  return await httpGet({ config: ctx.config, toolName: "get_quota" }, "/v1/quota");
@@ -1503,20 +1709,20 @@ var getQuotaTool = {
1503
1709
  };
1504
1710
 
1505
1711
  // src/tools/list-usage.ts
1506
- import { z as z35 } from "zod";
1507
- var inputSchema35 = {
1508
- type: z35.enum(BILLABLE_FEATURE_TYPES).optional().describe("Filter to a single billable feature type. Omit to return all types."),
1509
- search: z35.string().min(1).optional().describe("Case-insensitive partial match on the user's name or email."),
1510
- startDate: z35.string().regex(ISO_8601, "startDate must be an ISO-8601 date or timestamp (e.g. 2026-05-01 or 2026-05-01T00:00:00Z)").optional().describe("ISO-8601 date/timestamp; return only usage logs recorded on or after this time."),
1511
- endDate: z35.string().regex(ISO_8601, "endDate must be an ISO-8601 date or timestamp (e.g. 2026-05-31 or 2026-05-31T23:59:59Z)").optional().describe("ISO-8601 date/timestamp; return only usage logs recorded on or before this time."),
1512
- currentPage: z35.number().int().min(1).optional().describe("Page number, 1-indexed. Defaults to 1."),
1513
- pageSize: z35.number().int().min(1).max(200).optional().describe("Items per page (max 200). Defaults to 50.")
1712
+ import { z as z41 } from "zod";
1713
+ var inputSchema41 = {
1714
+ type: z41.enum(BILLABLE_FEATURE_TYPES).optional().describe("Filter to a single billable feature type. Omit to return all types."),
1715
+ search: z41.string().min(1).optional().describe("Case-insensitive partial match on the user's name or email."),
1716
+ startDate: z41.string().regex(ISO_8601, "startDate must be an ISO-8601 date or timestamp (e.g. 2026-05-01 or 2026-05-01T00:00:00Z)").optional().describe("ISO-8601 date/timestamp; return only usage logs recorded on or after this time."),
1717
+ endDate: z41.string().regex(ISO_8601, "endDate must be an ISO-8601 date or timestamp (e.g. 2026-05-31 or 2026-05-31T23:59:59Z)").optional().describe("ISO-8601 date/timestamp; return only usage logs recorded on or before this time."),
1718
+ currentPage: z41.number().int().min(1).optional().describe("Page number, 1-indexed. Defaults to 1."),
1719
+ pageSize: z41.number().int().min(1).max(200).optional().describe("Items per page (max 200). Defaults to 50.")
1514
1720
  };
1515
1721
  var listUsageTool = {
1516
1722
  name: "list_usage",
1517
1723
  title: "List Billable Usage Logs",
1518
1724
  description: "Use this when the user asks who consumed tokens, how their quota was spent, or wants an audit of billable activity over a date range. Retrieves a paginated list of billable feature usage logs for the organization \u2014 each entry is a token-consumption event (a dive, rank-radar creation, AI copywriter prompt, etc.). Filter by `type` (billable feature), `search` (user name/email), and `startDate`/`endDate`. Each item includes name, email, qty (tokens consumed), type, action (specific operation), nicheId/nicheName, rankRadarId, and date, plus pagination metadata (currentPage, pageSize, total, lastPage, hasNext, hasPrev).",
1519
- inputSchema: inputSchema35,
1725
+ inputSchema: inputSchema41,
1520
1726
  annotations: { readOnlyHint: true },
1521
1727
  handler: async (args, ctx) => {
1522
1728
  return await httpGet(
@@ -1546,6 +1752,7 @@ var allTools = [
1546
1752
  getRankRadarDataTool,
1547
1753
  getRankRadarSqpDataTool,
1548
1754
  getRankRadarPpcDataTool,
1755
+ listRankRadarTagsTool,
1549
1756
  createRankRadarTool,
1550
1757
  createNicheDiveTool,
1551
1758
  createNicheDiveFromCompetitorsListTool,
@@ -1558,6 +1765,11 @@ var allTools = [
1558
1765
  addRankRadarSearchTermsTool,
1559
1766
  pauseRankRadarSearchTermsTool,
1560
1767
  resumeRankRadarSearchTermsTool,
1768
+ createRankRadarTagTool,
1769
+ updateRankRadarTagTool,
1770
+ addRankRadarTagSearchTermsTool,
1771
+ removeRankRadarTagSearchTermsTool,
1772
+ deleteRankRadarTagTool,
1561
1773
  pauseRankRadarTool,
1562
1774
  resumeRankRadarTool,
1563
1775
  deleteRankRadarTool,
@@ -1577,6 +1789,13 @@ var allTools = [
1577
1789
 
1578
1790
  // src/whats-new.ts
1579
1791
  var WHATS_NEW = [
1792
+ {
1793
+ version: "0.17.1",
1794
+ released: "2026-10-13",
1795
+ items: [
1796
+ "Rank Radar tags end-to-end: list, create, update, delete tags, and attach or detach up to 1000 keywords per call (`list_rank_radar_tags`, `create_rank_radar_tag`, `update_rank_radar_tag`, `delete_rank_radar_tag`, `add_rank_radar_tag_search_terms`, `remove_rank_radar_tag_search_terms`). The highlights on each keyword in `get_rank_radar_data` are the same objects, so you can go from a ranking view to editing its tags without a round trip."
1797
+ ]
1798
+ },
1580
1799
  {
1581
1800
  version: "0.17.0",
1582
1801
  released: "2026-10-12",