@datadive-tools/mcp 0.17.0 → 0.19.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/index.js CHANGED
@@ -144,7 +144,7 @@ function extractMessage(body) {
144
144
  }
145
145
 
146
146
  // package.json
147
- var version = "0.17.0";
147
+ var version = "0.19.0";
148
148
 
149
149
  // src/http/client.ts
150
150
  var PKG_VERSION = version;
@@ -184,6 +184,7 @@ async function httpGet(ctx, path, query) {
184
184
  method: "GET",
185
185
  headers: {
186
186
  ...authHeaders(ctx.config),
187
+ ...hostHeaders(ctx.config),
187
188
  accept: "application/json",
188
189
  "user-agent": userAgent(ctx.toolName)
189
190
  }
@@ -201,6 +202,27 @@ async function httpPost(ctx, path, body) {
201
202
  method: "POST",
202
203
  headers: {
203
204
  ...authHeaders(ctx.config),
205
+ ...hostHeaders(ctx.config),
206
+ accept: "application/json",
207
+ "content-type": "application/json",
208
+ "user-agent": userAgent(ctx.toolName)
209
+ },
210
+ body: JSON.stringify(body)
211
+ });
212
+ } catch (e) {
213
+ throw networkError(url, e);
214
+ }
215
+ return handleResponse(res);
216
+ }
217
+ async function httpPatch(ctx, path, body) {
218
+ const url = buildUrl(ctx.config.baseUrl, path);
219
+ let res;
220
+ try {
221
+ res = await fetch(url, {
222
+ method: "PATCH",
223
+ headers: {
224
+ ...authHeaders(ctx.config),
225
+ ...hostHeaders(ctx.config),
204
226
  accept: "application/json",
205
227
  "content-type": "application/json",
206
228
  "user-agent": userAgent(ctx.toolName)
@@ -220,6 +242,7 @@ async function httpDelete(ctx, path) {
220
242
  method: "DELETE",
221
243
  headers: {
222
244
  ...authHeaders(ctx.config),
245
+ ...hostHeaders(ctx.config),
223
246
  accept: "application/json",
224
247
  "user-agent": userAgent(ctx.toolName)
225
248
  }
@@ -265,6 +288,14 @@ function authHeaders(config) {
265
288
  const c = config.credentials;
266
289
  return c.kind === "bearer" ? { authorization: `Bearer ${c.token}` } : { "x-api-key": c.apiKey };
267
290
  }
291
+ var RESERVED_HEADERS = /* @__PURE__ */ new Set(["authorization", "x-api-key", "accept", "content-type", "user-agent"]);
292
+ function hostHeaders(config) {
293
+ const extra = Object.entries(config.extraHeaders ?? {}).filter(([name]) => !RESERVED_HEADERS.has(name.toLowerCase()));
294
+ return {
295
+ ...Object.fromEntries(extra),
296
+ ...config.forwardedFor ? { "x-forwarded-for": config.forwardedFor } : {}
297
+ };
298
+ }
268
299
  function unwrap(body) {
269
300
  if (!isObject(body)) return body;
270
301
  if (!("data" in body)) return body;
@@ -431,6 +462,10 @@ var KEYWORD_EXCLUSIONS_MAX_PER_CALL = 100;
431
462
  var KEYWORD_EXCLUSIONS_MAX_PER_NICHE = 1e3;
432
463
  var RANK_RADAR_STATES = ["ACTIVE", "PAUSED", "ARCHIVED"];
433
464
  var RANK_RADAR_STATUSES = [...RANK_RADAR_STATES, "ALL"];
465
+ var TAG_TITLE_MAX_LENGTH = 100;
466
+ var TAG_KEYWORD_IDS_MAX = 1e3;
467
+ var TAG_RESERVED_COLOR = "#EB5757";
468
+ var TAG_HEX_COLOR_PATTERN = /^#[0-9A-Fa-f]{6}$/;
434
469
  var PPC_CAMPAIGN_BASE_SORT_BY = [
435
470
  "name",
436
471
  "state",
@@ -566,7 +601,7 @@ async function readAllKeywords(args, ctx) {
566
601
  var getRankRadarDataTool = {
567
602
  name: "get_rank_radar_data",
568
603
  title: "Get Keyword Rankings for a Rank Radar",
569
- 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\`.`,
604
+ 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\`.`,
570
605
  inputSchema: inputSchema8,
571
606
  annotations: { readOnlyHint: true },
572
607
  handler: async (args, ctx) => {
@@ -1170,17 +1205,193 @@ var resumeRankRadarSearchTermsTool = {
1170
1205
  }
1171
1206
  };
1172
1207
 
1173
- // src/tools/generate-listing-copy.ts
1208
+ // src/tools/list-rank-radar-tags.ts
1174
1209
  import { z as z26 } from "zod";
1210
+ var LIST_RANK_RADAR_TAGS_MAX_PAGE_SIZE = 50;
1175
1211
  var inputSchema27 = {
1176
- nicheId: z26.string().min(1).describe("The niche whose keyword data the copy is written from. Get one from `list_niches`."),
1177
- strategy: z26.enum(LISTING_COPY_STRATEGIES).describe(
1212
+ rankRadarId: z26.string().uuid().describe("The Rank Radar UUID whose tags to list (from `list_rank_radars`)."),
1213
+ currentPage: z26.number().int().min(1).optional().describe("Page number, 1-indexed. Defaults to 1."),
1214
+ 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.`)
1215
+ };
1216
+ var listRankRadarTagsTool = {
1217
+ name: "list_rank_radar_tags",
1218
+ title: "List Tags of a Rank Radar",
1219
+ 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.`,
1220
+ inputSchema: inputSchema27,
1221
+ annotations: { readOnlyHint: true },
1222
+ handler: async (args, ctx) => {
1223
+ return await httpGet(
1224
+ { config: ctx.config, toolName: "list_rank_radar_tags" },
1225
+ `/v1/niches/rank-radars/${encodeURIComponent(args.rankRadarId)}/tags`,
1226
+ {
1227
+ currentPage: args.currentPage,
1228
+ pageSize: args.pageSize
1229
+ }
1230
+ );
1231
+ }
1232
+ };
1233
+
1234
+ // src/tools/create-rank-radar-tag.ts
1235
+ import { z as z27 } from "zod";
1236
+ var inputSchema28 = {
1237
+ rankRadarId: z27.string().uuid().describe("The Rank Radar UUID to create the tag on (from `list_rank_radars`)."),
1238
+ title: z27.string().min(1).max(TAG_TITLE_MAX_LENGTH).describe(
1239
+ `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.`
1240
+ ),
1241
+ color: z27.string().regex(TAG_HEX_COLOR_PATTERN, "color must be in #RRGGBB form").optional().describe(
1242
+ `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.`
1243
+ ),
1244
+ description: z27.string().optional().describe("Free-form description, e.g. 'Prime Day run-up'."),
1245
+ startAt: z27.string().optional().describe("ISO 8601 start date, e.g. '2026-07-01'. Required if `endAt` is set; `endAt >= startAt`."),
1246
+ endAt: z27.string().optional().describe("ISO 8601 end date, e.g. '2026-07-14'. Must be greater than or equal to `startAt`."),
1247
+ rankRadarKeywordIds: z27.array(z27.string().uuid()).min(1).max(TAG_KEYWORD_IDS_MAX).optional().describe(
1248
+ `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\`.`
1249
+ )
1250
+ };
1251
+ var createRankRadarTagTool = {
1252
+ name: "create_rank_radar_tag",
1253
+ title: "Create a Rank Radar Tag",
1254
+ 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\`.`,
1255
+ inputSchema: inputSchema28,
1256
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
1257
+ handler: async (args, ctx) => {
1258
+ const body = { title: args.title };
1259
+ if (args.color !== void 0) body.color = args.color;
1260
+ if (args.description !== void 0) body.description = args.description;
1261
+ if (args.startAt !== void 0) body.startAt = args.startAt;
1262
+ if (args.endAt !== void 0) body.endAt = args.endAt;
1263
+ if (args.rankRadarKeywordIds !== void 0) body.rankRadarKeywordIds = args.rankRadarKeywordIds;
1264
+ return await httpPost(
1265
+ { config: ctx.config, toolName: "create_rank_radar_tag" },
1266
+ `/v1/niches/rank-radars/${encodeURIComponent(args.rankRadarId)}/tags`,
1267
+ body
1268
+ );
1269
+ }
1270
+ };
1271
+
1272
+ // src/tools/update-rank-radar-tag.ts
1273
+ import { z as z28 } from "zod";
1274
+ var inputSchema29 = {
1275
+ rankRadarId: z28.string().uuid().describe("The Rank Radar UUID the tag belongs to (from `list_rank_radars`)."),
1276
+ tagId: z28.string().uuid().describe("The tag UUID to update (the `id` from `list_rank_radar_tags` or `create_rank_radar_tag`)."),
1277
+ title: z28.string().min(1).max(TAG_TITLE_MAX_LENGTH).optional().describe(
1278
+ `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 }\`.`
1279
+ ),
1280
+ 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.`),
1281
+ description: z28.string().optional().describe("Free-form description. Pass an empty string to clear."),
1282
+ startAt: z28.string().optional().describe("ISO 8601 start date, e.g. '2026-07-01'. `endAt >= startAt`."),
1283
+ endAt: z28.string().optional().describe("ISO 8601 end date, e.g. '2026-07-14'. `endAt >= startAt`.")
1284
+ };
1285
+ var updateRankRadarTagTool = {
1286
+ name: "update_rank_radar_tag",
1287
+ title: "Update a Rank Radar Tag",
1288
+ 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).",
1289
+ inputSchema: inputSchema29,
1290
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
1291
+ handler: async (args, ctx) => {
1292
+ const body = {};
1293
+ if (args.title !== void 0) body.title = args.title;
1294
+ if (args.color !== void 0) body.color = args.color;
1295
+ if (args.description !== void 0) body.description = args.description;
1296
+ if (args.startAt !== void 0) body.startAt = args.startAt;
1297
+ if (args.endAt !== void 0) body.endAt = args.endAt;
1298
+ return await httpPatch(
1299
+ { config: ctx.config, toolName: "update_rank_radar_tag" },
1300
+ `/v1/niches/rank-radars/${encodeURIComponent(args.rankRadarId)}/tags/${encodeURIComponent(args.tagId)}`,
1301
+ body
1302
+ );
1303
+ }
1304
+ };
1305
+
1306
+ // src/tools/delete-rank-radar-tag.ts
1307
+ import { z as z29 } from "zod";
1308
+ var inputSchema30 = {
1309
+ rankRadarId: z29.string().uuid().describe("The Rank Radar UUID the tag belongs to (from `list_rank_radars`)."),
1310
+ tagId: z29.string().uuid().describe("The tag UUID to delete (the `id` from `list_rank_radar_tags`)."),
1311
+ confirm: z29.boolean().optional().describe(
1312
+ "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."
1313
+ )
1314
+ };
1315
+ var deleteRankRadarTagTool = {
1316
+ name: "delete_rank_radar_tag",
1317
+ title: "Delete a Rank Radar Tag",
1318
+ 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.',
1319
+ inputSchema: inputSchema30,
1320
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
1321
+ handler: async (args, ctx) => {
1322
+ const pending = requireConfirmation(
1323
+ args.confirm,
1324
+ ctx,
1325
+ "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`."
1326
+ );
1327
+ if (pending) return pending;
1328
+ await httpDelete(
1329
+ { config: ctx.config, toolName: "delete_rank_radar_tag" },
1330
+ `/v1/niches/rank-radars/${encodeURIComponent(args.rankRadarId)}/tags/${encodeURIComponent(args.tagId)}`
1331
+ );
1332
+ return { status: "deleted", rankRadarId: args.rankRadarId, tagId: args.tagId };
1333
+ }
1334
+ };
1335
+
1336
+ // src/tools/add-rank-radar-tag-search-terms.ts
1337
+ import { z as z30 } from "zod";
1338
+ var inputSchema31 = {
1339
+ rankRadarId: z30.string().uuid().describe("The Rank Radar UUID the tag belongs to (from `list_rank_radars`)."),
1340
+ tagId: z30.string().uuid().describe("The tag UUID to attach keywords to (the `id` from `list_rank_radar_tags`)."),
1341
+ rankRadarKeywordIds: z30.array(z30.string().uuid()).min(1).max(TAG_KEYWORD_IDS_MAX).describe(
1342
+ `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.`
1343
+ )
1344
+ };
1345
+ var addRankRadarTagSearchTermsTool = {
1346
+ name: "add_rank_radar_tag_search_terms",
1347
+ title: "Attach Search Terms to a Rank Radar Tag",
1348
+ 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.`,
1349
+ inputSchema: inputSchema31,
1350
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
1351
+ handler: async (args, ctx) => {
1352
+ return await httpPost(
1353
+ { config: ctx.config, toolName: "add_rank_radar_tag_search_terms" },
1354
+ `/v1/niches/rank-radars/${encodeURIComponent(args.rankRadarId)}/tags/${encodeURIComponent(args.tagId)}/search-terms`,
1355
+ { rankRadarKeywordIds: args.rankRadarKeywordIds }
1356
+ );
1357
+ }
1358
+ };
1359
+
1360
+ // src/tools/remove-rank-radar-tag-search-terms.ts
1361
+ import { z as z31 } from "zod";
1362
+ var inputSchema32 = {
1363
+ rankRadarId: z31.string().uuid().describe("The Rank Radar UUID the tag belongs to (from `list_rank_radars`)."),
1364
+ tagId: z31.string().uuid().describe("The tag UUID to detach keywords from (the `id` from `list_rank_radar_tags`)."),
1365
+ rankRadarKeywordIds: z31.array(z31.string().uuid()).min(1).max(TAG_KEYWORD_IDS_MAX).describe(
1366
+ `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.`
1367
+ )
1368
+ };
1369
+ var removeRankRadarTagSearchTermsTool = {
1370
+ name: "remove_rank_radar_tag_search_terms",
1371
+ title: "Detach Search Terms from a Rank Radar Tag",
1372
+ 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.`,
1373
+ inputSchema: inputSchema32,
1374
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
1375
+ handler: async (args, ctx) => {
1376
+ return await httpPost(
1377
+ { config: ctx.config, toolName: "remove_rank_radar_tag_search_terms" },
1378
+ `/v1/niches/rank-radars/${encodeURIComponent(args.rankRadarId)}/tags/${encodeURIComponent(args.tagId)}/search-terms/remove`,
1379
+ { rankRadarKeywordIds: args.rankRadarKeywordIds }
1380
+ );
1381
+ }
1382
+ };
1383
+
1384
+ // src/tools/generate-listing-copy.ts
1385
+ import { z as z32 } from "zod";
1386
+ var inputSchema33 = {
1387
+ nicheId: z32.string().min(1).describe("The niche whose keyword data the copy is written from. Get one from `list_niches`."),
1388
+ strategy: z32.enum(LISTING_COPY_STRATEGIES).describe(
1178
1389
  "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."
1179
1390
  ),
1180
- currentListing: z26.record(z26.union([z26.string(), z26.array(z26.string())])).describe(
1391
+ currentListing: z32.record(z32.union([z32.string(), z32.array(z32.string())])).describe(
1181
1392
  '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.'
1182
1393
  ),
1183
- confirm: z26.boolean().optional().describe(
1394
+ confirm: z32.boolean().optional().describe(
1184
1395
  "Must be true to proceed \u2014 each generation spends one AI Copywriter prompt from the quota. Confirm the cost with the user first."
1185
1396
  )
1186
1397
  };
@@ -1188,7 +1399,7 @@ var generateListingCopyTool = {
1188
1399
  name: "generate_listing_copy",
1189
1400
  title: "Generate Listing Copy for a Niche",
1190
1401
  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`.",
1191
- inputSchema: inputSchema27,
1402
+ inputSchema: inputSchema33,
1192
1403
  annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
1193
1404
  handler: async (args, ctx) => {
1194
1405
  const pending = requireConfirmation(
@@ -1206,16 +1417,16 @@ var generateListingCopyTool = {
1206
1417
  };
1207
1418
 
1208
1419
  // src/tools/get-listing-copy-generation-status.ts
1209
- import { z as z27 } from "zod";
1210
- var inputSchema28 = {
1211
- nicheId: z27.string().min(1).describe("The niche the generation was started for."),
1212
- generationId: z27.string().min(1).describe("The `generationId` returned by `generate_listing_copy`.")
1420
+ import { z as z33 } from "zod";
1421
+ var inputSchema34 = {
1422
+ nicheId: z33.string().min(1).describe("The niche the generation was started for."),
1423
+ generationId: z33.string().min(1).describe("The `generationId` returned by `generate_listing_copy`.")
1213
1424
  };
1214
1425
  var getListingCopyGenerationStatusTool = {
1215
1426
  name: "get_listing_copy_generation_status",
1216
1427
  title: "Get Listing Copy Generation Status",
1217
1428
  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`.",
1218
- inputSchema: inputSchema28,
1429
+ inputSchema: inputSchema34,
1219
1430
  annotations: { readOnlyHint: true },
1220
1431
  handler: async (args, ctx) => {
1221
1432
  return await httpGet(
@@ -1226,16 +1437,16 @@ var getListingCopyGenerationStatusTool = {
1226
1437
  };
1227
1438
 
1228
1439
  // src/tools/list-seller-profiles.ts
1229
- import { z as z28 } from "zod";
1230
- var inputSchema29 = {
1231
- currentPage: z28.number().int().min(1).optional().describe("Page number, 1-indexed. Defaults to 1."),
1232
- pageSize: z28.number().int().min(1).max(50).optional().describe("Items per page (max 50). Defaults to 20.")
1440
+ import { z as z34 } from "zod";
1441
+ var inputSchema35 = {
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.")
1233
1444
  };
1234
1445
  var listSellerProfilesTool = {
1235
1446
  name: "list_seller_profiles",
1236
1447
  title: "List Connected Amazon Seller Profiles",
1237
1448
  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).',
1238
- inputSchema: inputSchema29,
1449
+ inputSchema: inputSchema35,
1239
1450
  annotations: { readOnlyHint: true },
1240
1451
  handler: async (args, ctx) => {
1241
1452
  return await httpGet(
@@ -1250,15 +1461,15 @@ var listSellerProfilesTool = {
1250
1461
  };
1251
1462
 
1252
1463
  // src/tools/get-seller-catalog.ts
1253
- import { z as z30 } from "zod";
1464
+ import { z as z36 } from "zod";
1254
1465
 
1255
1466
  // src/tools/seller-scoped.ts
1256
- import { z as z29 } from "zod";
1467
+ import { z as z35 } from "zod";
1257
1468
  var sellerScopedInputSchema = {
1258
- sellerId: z29.string().min(1).describe(
1469
+ sellerId: z35.string().min(1).describe(
1259
1470
  "Amazon seller account ID. Get it from `list_seller_profiles`, or find it on the Connections page at https://2.datadive.tools."
1260
1471
  ),
1261
- marketplace: z29.enum(SUPPORTED_MARKETPLACES).describe('Amazon marketplace code, e.g. "com" for amazon.com or "co.uk" for amazon.co.uk.')
1472
+ marketplace: z35.enum(SUPPORTED_MARKETPLACES).describe('Amazon marketplace code, e.g. "com" for amazon.com or "co.uk" for amazon.co.uk.')
1262
1473
  };
1263
1474
  function sellerMarketplacePath(sellerId, marketplace, suffix) {
1264
1475
  return `/v1/seller_profiles/${encodeURIComponent(sellerId)}/marketplaces/${encodeURIComponent(
@@ -1267,19 +1478,19 @@ function sellerMarketplacePath(sellerId, marketplace, suffix) {
1267
1478
  }
1268
1479
 
1269
1480
  // src/tools/get-seller-catalog.ts
1270
- var inputSchema30 = {
1481
+ var inputSchema36 = {
1271
1482
  ...sellerScopedInputSchema,
1272
- search: z30.string().min(1).optional().describe("Case-insensitive partial match on product title or brand."),
1273
- brand: z30.string().min(1).optional().describe("Case-insensitive partial match on brand name."),
1274
- status: z30.enum(CATALOG_STATUSES).optional().describe('Listing status filter. "Active" (default) or "all".'),
1275
- currentPage: z30.number().int().min(1).optional().describe("Page number, 1-indexed. Defaults to 1."),
1276
- pageSize: z30.number().int().min(1).max(50).optional().describe("Items per page (max 50). Defaults to 20.")
1483
+ search: z36.string().min(1).optional().describe("Case-insensitive partial match on product title or brand."),
1484
+ brand: z36.string().min(1).optional().describe("Case-insensitive partial match on brand name."),
1485
+ status: z36.enum(CATALOG_STATUSES).optional().describe('Listing status filter. "Active" (default) or "all".'),
1486
+ currentPage: z36.number().int().min(1).optional().describe("Page number, 1-indexed. Defaults to 1."),
1487
+ pageSize: z36.number().int().min(1).max(50).optional().describe("Items per page (max 50). Defaults to 20.")
1277
1488
  };
1278
1489
  var getSellerCatalogTool = {
1279
1490
  name: "get_seller_catalog",
1280
1491
  title: "List Catalog ASINs for a Seller",
1281
1492
  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).",
1282
- inputSchema: inputSchema30,
1493
+ inputSchema: inputSchema36,
1283
1494
  annotations: { readOnlyHint: true },
1284
1495
  handler: async (args, ctx) => {
1285
1496
  return await httpGet(
@@ -1297,28 +1508,28 @@ var getSellerCatalogTool = {
1297
1508
  };
1298
1509
 
1299
1510
  // src/tools/get-seller-listing-changes.ts
1300
- import { z as z31 } from "zod";
1301
- var inputSchema31 = {
1511
+ import { z as z37 } from "zod";
1512
+ var inputSchema37 = {
1302
1513
  ...sellerScopedInputSchema,
1303
- 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".'),
1304
- asin: z31.string().min(1).optional().describe("Filter to an exact ASIN or parent ASIN."),
1305
- brand: z31.string().min(1).optional().describe("Case-insensitive partial match on brand name."),
1306
- search: z31.string().min(1).optional().describe("Case-insensitive partial match on product title or brand."),
1307
- 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."),
1308
- 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."),
1309
- sortBy: z31.enum(LISTING_CHANGE_SORT_BY).optional().describe('Sort field. "date" (default) or "type".'),
1310
- sortOrder: z31.enum(SORT_ORDER).optional().describe('Sort direction. "DESC" (default) or "ASC".'),
1311
- includeCorrelations: z31.boolean().optional().describe(
1514
+ 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".'),
1515
+ asin: z37.string().min(1).optional().describe("Filter to an exact ASIN or parent ASIN."),
1516
+ brand: z37.string().min(1).optional().describe("Case-insensitive partial match on brand name."),
1517
+ search: z37.string().min(1).optional().describe("Case-insensitive partial match on product title or brand."),
1518
+ 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."),
1519
+ 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."),
1520
+ sortBy: z37.enum(LISTING_CHANGE_SORT_BY).optional().describe('Sort field. "date" (default) or "type".'),
1521
+ sortOrder: z37.enum(SORT_ORDER).optional().describe('Sort direction. "DESC" (default) or "ASC".'),
1522
+ includeCorrelations: z37.boolean().optional().describe(
1312
1523
  "When true, include the ranking/conversion `correlation` object per change (CVR and search-term movement before vs after). Defaults to false."
1313
1524
  ),
1314
- currentPage: z31.number().int().min(1).optional().describe("Page number, 1-indexed. Defaults to 1."),
1315
- pageSize: z31.number().int().min(1).max(50).optional().describe("Items per page (max 50). Defaults to 20.")
1525
+ currentPage: z37.number().int().min(1).optional().describe("Page number, 1-indexed. Defaults to 1."),
1526
+ pageSize: z37.number().int().min(1).max(50).optional().describe("Items per page (max 50). Defaults to 20.")
1316
1527
  };
1317
1528
  var getSellerListingChangesTool = {
1318
1529
  name: "get_seller_listing_changes",
1319
1530
  title: "List Listing Changes for a Seller",
1320
1531
  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).",
1321
- inputSchema: inputSchema31,
1532
+ inputSchema: inputSchema37,
1322
1533
  annotations: { readOnlyHint: true },
1323
1534
  handler: async (args, ctx) => {
1324
1535
  return await httpGet(
@@ -1343,40 +1554,40 @@ var getSellerListingChangesTool = {
1343
1554
  };
1344
1555
 
1345
1556
  // src/tools/list-ppc-campaigns.ts
1346
- import { z as z32 } from "zod";
1557
+ import { z as z38 } from "zod";
1347
1558
  var PPC_CAMPAIGNS_MAX_WINDOW_DAYS = 90;
1348
1559
  var PPC_CAMPAIGNS_MAX_PAGE_SIZE = 50;
1349
- var inputSchema32 = {
1560
+ var inputSchema38 = {
1350
1561
  ...sellerScopedInputSchema,
1351
- startDate: z32.string().regex(
1562
+ startDate: z38.string().regex(
1352
1563
  ISO_8601,
1353
1564
  "startDate must be an ISO-8601 date or timestamp (e.g. 2026-05-01 or 2026-05-01T00:00:00Z)"
1354
1565
  ).optional().describe("Start of the reporting window, ISO-8601. Defaults to 30 days before endDate."),
1355
- endDate: z32.string().regex(
1566
+ endDate: z38.string().regex(
1356
1567
  ISO_8601,
1357
1568
  "endDate must be an ISO-8601 date or timestamp (e.g. 2026-05-31 or 2026-05-31T23:59:59Z)"
1358
1569
  ).optional().describe(
1359
1570
  `End of the reporting window, ISO-8601. Defaults to now. At most ${PPC_CAMPAIGNS_MAX_WINDOW_DAYS} days after startDate.`
1360
1571
  ),
1361
- asin: z32.string().min(1).optional().describe("Only campaigns advertising this ASIN. Cannot be combined with parentAsin."),
1362
- parentAsin: z32.string().min(1).optional().describe("Only campaigns advertising any ASIN of this variation family. Cannot be combined with asin."),
1363
- search: z32.string().min(3).max(200).optional().describe("Partial match on campaign name or advertised ASIN. At least 3 characters."),
1364
- includePlacements: z32.boolean().optional().describe(
1572
+ asin: z38.string().min(1).optional().describe("Only campaigns advertising this ASIN. Cannot be combined with parentAsin."),
1573
+ parentAsin: z38.string().min(1).optional().describe("Only campaigns advertising any ASIN of this variation family. Cannot be combined with asin."),
1574
+ search: z38.string().min(3).max(200).optional().describe("Partial match on campaign name or advertised ASIN. At least 3 characters."),
1575
+ includePlacements: z38.boolean().optional().describe(
1365
1576
  "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."
1366
1577
  ),
1367
- sortBy: z32.enum([...PPC_CAMPAIGN_BASE_SORT_BY, ...PPC_CAMPAIGN_PLACEMENT_SORT_BY]).optional().describe(
1578
+ sortBy: z38.enum([...PPC_CAMPAIGN_BASE_SORT_BY, ...PPC_CAMPAIGN_PLACEMENT_SORT_BY]).optional().describe(
1368
1579
  `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.`
1369
1580
  ),
1370
- sortOrder: z32.enum(SORT_ORDER).optional().describe('Sort direction. "ASC" (default) or "DESC".'),
1371
- state: z32.enum(PPC_CAMPAIGN_STATES).optional().describe("Only ENABLED or only PAUSED campaigns. Omit for both."),
1372
- currentPage: z32.number().int().min(1).optional().describe("Page number, 1-indexed. Defaults to 1."),
1373
- 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.`)
1581
+ sortOrder: z38.enum(SORT_ORDER).optional().describe('Sort direction. "ASC" (default) or "DESC".'),
1582
+ state: z38.enum(PPC_CAMPAIGN_STATES).optional().describe("Only ENABLED or only PAUSED campaigns. Omit for both."),
1583
+ currentPage: z38.number().int().min(1).optional().describe("Page number, 1-indexed. Defaults to 1."),
1584
+ 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.`)
1374
1585
  };
1375
1586
  var listPpcCampaignsTool = {
1376
1587
  name: "list_ppc_campaigns",
1377
1588
  title: "List PPC Campaigns for a Seller",
1378
1589
  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\`.`,
1379
- inputSchema: inputSchema32,
1590
+ inputSchema: inputSchema38,
1380
1591
  annotations: { readOnlyHint: true },
1381
1592
  handler: async (args, ctx) => {
1382
1593
  if (args.asin && args.parentAsin) {
@@ -1407,21 +1618,21 @@ var listPpcCampaignsTool = {
1407
1618
  };
1408
1619
 
1409
1620
  // src/tools/get-asin-inventory-distribution.ts
1410
- import { z as z33 } from "zod";
1411
- var inputSchema33 = {
1412
- sellerId: z33.string().min(1).describe(
1621
+ import { z as z39 } from "zod";
1622
+ var inputSchema39 = {
1623
+ sellerId: z39.string().min(1).describe(
1413
1624
  "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."
1414
1625
  ),
1415
- marketplace: z33.enum(SUPPORTED_MARKETPLACES).describe('Amazon marketplace code, e.g. "com" for amazon.com or "co.uk" for amazon.co.uk.'),
1416
- asin: z33.string().regex(/^[A-Z0-9]{10}$/, "ASIN must be 10 uppercase alphanumeric characters (e.g. B08XYZ1234).").describe(
1626
+ marketplace: z39.enum(SUPPORTED_MARKETPLACES).describe('Amazon marketplace code, e.g. "com" for amazon.com or "co.uk" for amazon.co.uk.'),
1627
+ asin: z39.string().regex(/^[A-Z0-9]{10}$/, "ASIN must be 10 uppercase alphanumeric characters (e.g. B08XYZ1234).").describe(
1417
1628
  "Amazon Standard Identification Number (10 uppercase alphanumeric characters, e.g. B08XYZ1234)."
1418
1629
  )
1419
1630
  };
1420
1631
  var getAsinInventoryDistributionTool = {
1421
1632
  name: "get_asin_inventory_distribution",
1422
- title: "Get Per-Fulfillment-Center Inventory Distribution for an ASIN",
1423
- 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.",
1424
- inputSchema: inputSchema33,
1633
+ title: "Get Inventory Metrics and Fulfillment-Center Distribution for an ASIN",
1634
+ 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.",
1635
+ inputSchema: inputSchema39,
1425
1636
  annotations: { readOnlyHint: true },
1426
1637
  handler: async (args, ctx) => {
1427
1638
  return await httpGet(
@@ -1434,18 +1645,18 @@ var getAsinInventoryDistributionTool = {
1434
1645
  };
1435
1646
 
1436
1647
  // src/tools/alert-query.ts
1437
- import { z as z34 } from "zod";
1648
+ import { z as z40 } from "zod";
1438
1649
  var alertsQueryInputSchema = {
1439
- sellerId: z34.string().min(1).max(50).optional().describe('Filter to a single connected Amazon seller account ID, e.g. "A1B2C3D4E5".'),
1440
- 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.'),
1441
- status: z34.enum(ALERT_STATUSES).optional().describe(
1650
+ sellerId: z40.string().min(1).max(50).optional().describe('Filter to a single connected Amazon seller account ID, e.g. "A1B2C3D4E5".'),
1651
+ 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.'),
1652
+ status: z40.enum(ALERT_STATUSES).optional().describe(
1442
1653
  "Lifecycle filter. `active` (default) returns unresolved alerts, `resolved` returns resolved alerts, `all` returns both. Dismissed alerts are never returned."
1443
1654
  ),
1444
- 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(
1655
+ 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(
1445
1656
  "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."
1446
1657
  ),
1447
- currentPage: z34.number().int().min(1).optional().describe("Page number, 1-indexed. Defaults to 1."),
1448
- pageSize: z34.number().int().min(1).max(50).optional().describe("Items per page (max 50). Defaults to 20.")
1658
+ currentPage: z40.number().int().min(1).optional().describe("Page number, 1-indexed. Defaults to 1."),
1659
+ pageSize: z40.number().int().min(1).max(50).optional().describe("Items per page (max 50). Defaults to 20.")
1449
1660
  };
1450
1661
 
1451
1662
  // src/tools/list-indexing-issue-alerts.ts
@@ -1495,12 +1706,12 @@ var listBlindSpendAlertsTool = {
1495
1706
  };
1496
1707
 
1497
1708
  // src/tools/get-quota.ts
1498
- var inputSchema34 = {};
1709
+ var inputSchema40 = {};
1499
1710
  var getQuotaTool = {
1500
1711
  name: "get_quota",
1501
1712
  title: "Get Quota Usage",
1502
1713
  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.",
1503
- inputSchema: inputSchema34,
1714
+ inputSchema: inputSchema40,
1504
1715
  annotations: { readOnlyHint: true },
1505
1716
  handler: async (_args, ctx) => {
1506
1717
  return await httpGet({ config: ctx.config, toolName: "get_quota" }, "/v1/quota");
@@ -1508,20 +1719,20 @@ var getQuotaTool = {
1508
1719
  };
1509
1720
 
1510
1721
  // src/tools/list-usage.ts
1511
- import { z as z35 } from "zod";
1512
- var inputSchema35 = {
1513
- type: z35.enum(BILLABLE_FEATURE_TYPES).optional().describe("Filter to a single billable feature type. Omit to return all types."),
1514
- search: z35.string().min(1).optional().describe("Case-insensitive partial match on the user's name or email."),
1515
- 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."),
1516
- 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."),
1517
- currentPage: z35.number().int().min(1).optional().describe("Page number, 1-indexed. Defaults to 1."),
1518
- pageSize: z35.number().int().min(1).max(200).optional().describe("Items per page (max 200). Defaults to 50.")
1722
+ import { z as z41 } from "zod";
1723
+ var inputSchema41 = {
1724
+ type: z41.enum(BILLABLE_FEATURE_TYPES).optional().describe("Filter to a single billable feature type. Omit to return all types."),
1725
+ search: z41.string().min(1).optional().describe("Case-insensitive partial match on the user's name or email."),
1726
+ 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."),
1727
+ 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."),
1728
+ currentPage: z41.number().int().min(1).optional().describe("Page number, 1-indexed. Defaults to 1."),
1729
+ pageSize: z41.number().int().min(1).max(200).optional().describe("Items per page (max 200). Defaults to 50.")
1519
1730
  };
1520
1731
  var listUsageTool = {
1521
1732
  name: "list_usage",
1522
1733
  title: "List Billable Usage Logs",
1523
1734
  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).",
1524
- inputSchema: inputSchema35,
1735
+ inputSchema: inputSchema41,
1525
1736
  annotations: { readOnlyHint: true },
1526
1737
  handler: async (args, ctx) => {
1527
1738
  return await httpGet(
@@ -1551,6 +1762,7 @@ var allTools = [
1551
1762
  getRankRadarDataTool,
1552
1763
  getRankRadarSqpDataTool,
1553
1764
  getRankRadarPpcDataTool,
1765
+ listRankRadarTagsTool,
1554
1766
  createRankRadarTool,
1555
1767
  createNicheDiveTool,
1556
1768
  createNicheDiveFromCompetitorsListTool,
@@ -1563,6 +1775,11 @@ var allTools = [
1563
1775
  addRankRadarSearchTermsTool,
1564
1776
  pauseRankRadarSearchTermsTool,
1565
1777
  resumeRankRadarSearchTermsTool,
1778
+ createRankRadarTagTool,
1779
+ updateRankRadarTagTool,
1780
+ addRankRadarTagSearchTermsTool,
1781
+ removeRankRadarTagSearchTermsTool,
1782
+ deleteRankRadarTagTool,
1566
1783
  pauseRankRadarTool,
1567
1784
  resumeRankRadarTool,
1568
1785
  deleteRankRadarTool,
@@ -1582,6 +1799,13 @@ var allTools = [
1582
1799
 
1583
1800
  // src/whats-new.ts
1584
1801
  var WHATS_NEW = [
1802
+ {
1803
+ version: "0.17.1",
1804
+ released: "2026-10-13",
1805
+ items: [
1806
+ "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."
1807
+ ]
1808
+ },
1585
1809
  {
1586
1810
  version: "0.17.0",
1587
1811
  released: "2026-10-12",