@datadive-tools/mcp 0.18.0 → 0.20.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/README.md +12 -2
- package/dist/index.js +734 -389
- package/dist/index.js.map +1 -1
- package/dist/lib.d.ts +7 -0
- package/dist/lib.js +734 -389
- package/dist/lib.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -27,7 +27,7 @@ function loadConfig(env = process.env) {
|
|
|
27
27
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
28
28
|
|
|
29
29
|
// src/tools/list-niches.ts
|
|
30
|
-
import { z } from "zod";
|
|
30
|
+
import { z as z2 } from "zod";
|
|
31
31
|
|
|
32
32
|
// src/http/errors.ts
|
|
33
33
|
var KEY_HELP_URL2 = "https://2.datadive.tools/api-key";
|
|
@@ -144,7 +144,7 @@ function extractMessage(body) {
|
|
|
144
144
|
}
|
|
145
145
|
|
|
146
146
|
// package.json
|
|
147
|
-
var version = "0.
|
|
147
|
+
var version = "0.20.0";
|
|
148
148
|
|
|
149
149
|
// src/http/client.ts
|
|
150
150
|
var PKG_VERSION = version;
|
|
@@ -184,7 +184,7 @@ async function httpGet(ctx, path, query) {
|
|
|
184
184
|
method: "GET",
|
|
185
185
|
headers: {
|
|
186
186
|
...authHeaders(ctx.config),
|
|
187
|
-
...
|
|
187
|
+
...hostHeaders(ctx.config),
|
|
188
188
|
accept: "application/json",
|
|
189
189
|
"user-agent": userAgent(ctx.toolName)
|
|
190
190
|
}
|
|
@@ -202,7 +202,7 @@ async function httpPost(ctx, path, body) {
|
|
|
202
202
|
method: "POST",
|
|
203
203
|
headers: {
|
|
204
204
|
...authHeaders(ctx.config),
|
|
205
|
-
...
|
|
205
|
+
...hostHeaders(ctx.config),
|
|
206
206
|
accept: "application/json",
|
|
207
207
|
"content-type": "application/json",
|
|
208
208
|
"user-agent": userAgent(ctx.toolName)
|
|
@@ -222,7 +222,7 @@ async function httpPatch(ctx, path, body) {
|
|
|
222
222
|
method: "PATCH",
|
|
223
223
|
headers: {
|
|
224
224
|
...authHeaders(ctx.config),
|
|
225
|
-
...
|
|
225
|
+
...hostHeaders(ctx.config),
|
|
226
226
|
accept: "application/json",
|
|
227
227
|
"content-type": "application/json",
|
|
228
228
|
"user-agent": userAgent(ctx.toolName)
|
|
@@ -242,7 +242,7 @@ async function httpDelete(ctx, path) {
|
|
|
242
242
|
method: "DELETE",
|
|
243
243
|
headers: {
|
|
244
244
|
...authHeaders(ctx.config),
|
|
245
|
-
...
|
|
245
|
+
...hostHeaders(ctx.config),
|
|
246
246
|
accept: "application/json",
|
|
247
247
|
"user-agent": userAgent(ctx.toolName)
|
|
248
248
|
}
|
|
@@ -276,6 +276,10 @@ function buildUrl(baseUrl, path, query) {
|
|
|
276
276
|
if (query) {
|
|
277
277
|
for (const [k, v] of Object.entries(query)) {
|
|
278
278
|
if (v === void 0 || v === null) continue;
|
|
279
|
+
if (Array.isArray(v)) {
|
|
280
|
+
for (const item of v) u.searchParams.append(k, String(item));
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
279
283
|
u.searchParams.set(k, String(v));
|
|
280
284
|
}
|
|
281
285
|
}
|
|
@@ -288,8 +292,13 @@ function authHeaders(config) {
|
|
|
288
292
|
const c = config.credentials;
|
|
289
293
|
return c.kind === "bearer" ? { authorization: `Bearer ${c.token}` } : { "x-api-key": c.apiKey };
|
|
290
294
|
}
|
|
291
|
-
|
|
292
|
-
|
|
295
|
+
var RESERVED_HEADERS = /* @__PURE__ */ new Set(["authorization", "x-api-key", "accept", "content-type", "user-agent"]);
|
|
296
|
+
function hostHeaders(config) {
|
|
297
|
+
const extra = Object.entries(config.extraHeaders ?? {}).filter(([name]) => !RESERVED_HEADERS.has(name.toLowerCase()));
|
|
298
|
+
return {
|
|
299
|
+
...Object.fromEntries(extra),
|
|
300
|
+
...config.forwardedFor ? { "x-forwarded-for": config.forwardedFor } : {}
|
|
301
|
+
};
|
|
293
302
|
}
|
|
294
303
|
function unwrap(body) {
|
|
295
304
|
if (!isObject(body)) return body;
|
|
@@ -303,24 +312,111 @@ function isObject(v) {
|
|
|
303
312
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
304
313
|
}
|
|
305
314
|
|
|
315
|
+
// src/tools/niche-tag-ids.ts
|
|
316
|
+
import { z } from "zod";
|
|
317
|
+
|
|
318
|
+
// src/types/api.ts
|
|
319
|
+
var ISO_8601 = /^\d{4}-\d{2}-\d{2}(?:T\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?(?:Z|[+-]\d{2}:?\d{2})?)?$/;
|
|
320
|
+
var SUPPORTED_MARKETPLACES = [
|
|
321
|
+
"com",
|
|
322
|
+
"ca",
|
|
323
|
+
"co.uk",
|
|
324
|
+
"com.mx",
|
|
325
|
+
"in",
|
|
326
|
+
"fr",
|
|
327
|
+
"de",
|
|
328
|
+
"es",
|
|
329
|
+
"it",
|
|
330
|
+
"co.jp"
|
|
331
|
+
];
|
|
332
|
+
var NICHE_TAG_IDS_MAX_PER_CALL = 100;
|
|
333
|
+
var NICHE_KEYWORDS_MAX_PER_CALL = 100;
|
|
334
|
+
var NICHE_KEYWORD_MAX_LENGTH = 255;
|
|
335
|
+
var KEYWORD_EXCLUSIONS_MAX_PER_CALL = 100;
|
|
336
|
+
var KEYWORD_EXCLUSIONS_MAX_PER_NICHE = 1e3;
|
|
337
|
+
var RANK_RADAR_STATES = ["ACTIVE", "PAUSED", "ARCHIVED"];
|
|
338
|
+
var RANK_RADAR_STATUSES = [...RANK_RADAR_STATES, "ALL"];
|
|
339
|
+
var TAG_TITLE_MAX_LENGTH = 100;
|
|
340
|
+
var TAG_KEYWORD_IDS_MAX = 1e3;
|
|
341
|
+
var TAG_RESERVED_COLOR = "#EB5757";
|
|
342
|
+
var TAG_HEX_COLOR_PATTERN = /^#[0-9A-Fa-f]{6}$/;
|
|
343
|
+
var PPC_CAMPAIGN_BASE_SORT_BY = [
|
|
344
|
+
"name",
|
|
345
|
+
"state",
|
|
346
|
+
"bidStrategy",
|
|
347
|
+
"budget",
|
|
348
|
+
"impressions",
|
|
349
|
+
"clicks",
|
|
350
|
+
"ctr",
|
|
351
|
+
"cpc",
|
|
352
|
+
"spend",
|
|
353
|
+
"unitsSold",
|
|
354
|
+
"sales",
|
|
355
|
+
"acos",
|
|
356
|
+
"cvr",
|
|
357
|
+
"tosImpressionShare"
|
|
358
|
+
];
|
|
359
|
+
var PPC_CAMPAIGN_PLACEMENT_SORT_BY = [
|
|
360
|
+
...["tos", "ros", "pp", "off"].flatMap(
|
|
361
|
+
(p) => ["Impressions", "Clicks", "Spend", "Sales", "UnitsSold", "Ctr", "Cpc", "Acos"].map(
|
|
362
|
+
(m) => `${p}${m}`
|
|
363
|
+
)
|
|
364
|
+
),
|
|
365
|
+
"tosBidAdjustment",
|
|
366
|
+
"rosBidAdjustment",
|
|
367
|
+
"ppBidAdjustment"
|
|
368
|
+
];
|
|
369
|
+
var PPC_CAMPAIGN_STATES = ["ENABLED", "PAUSED"];
|
|
370
|
+
var ALERT_STATUSES = ["active", "resolved", "all"];
|
|
371
|
+
var BILLABLE_FEATURE_TYPES = [
|
|
372
|
+
"DIVED_ASINS",
|
|
373
|
+
"PRODUCT_BRIEF_ASINS",
|
|
374
|
+
"AI_COPYWRITER_PROMPTS",
|
|
375
|
+
"RANK_RADAR_KEYWORDS"
|
|
376
|
+
];
|
|
377
|
+
var CATALOG_STATUSES = ["Active", "all"];
|
|
378
|
+
var LISTING_CHANGE_TYPES = ["Price", "Content", "Image"];
|
|
379
|
+
var LISTING_CHANGE_SORT_BY = ["date", "type"];
|
|
380
|
+
var SORT_ORDER = ["ASC", "DESC"];
|
|
381
|
+
var ADD_SEARCH_TERMS_MAX = 1e3;
|
|
382
|
+
var LISTING_COPY_STRATEGIES = ["cosmo", "ranking-juice", "nlp", "cosmo-rufus"];
|
|
383
|
+
var FAST_KEYWORDS_MAX_PAGE_SIZE = 100;
|
|
384
|
+
|
|
385
|
+
// src/tools/niche-tag-ids.ts
|
|
386
|
+
var tagId = () => z.number().int().min(1);
|
|
387
|
+
function unique(schema, field) {
|
|
388
|
+
return schema.refine((ids) => new Set(ids).size === ids.length, {
|
|
389
|
+
message: `${field} must not contain the same id twice`
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
var nicheTagIdList = (field) => unique(z.array(tagId()).min(1).max(NICHE_TAG_IDS_MAX_PER_CALL), field);
|
|
393
|
+
var tagFilterIdList = (field) => unique(z.array(tagId()).max(NICHE_TAG_IDS_MAX_PER_CALL), field);
|
|
394
|
+
var NICHE_TAG_WRITE_RULES = "Only the organization owner or a team admin can change a niche's Spaces and labels; anyone else is refused with a 403 (INSUFFICIENT_ROLE), so tell the user instead of retrying. A change is all or nothing: if any id is unknown, or a Space is one the user has only view rights on, the whole call fails and nothing changes (the error lists the offending ids). Ids already in the wanted state are counted, never errors, so a repeat is safe. Costs nothing. Limited to 2 calls per minute per niche, so put every id in one call.";
|
|
395
|
+
|
|
306
396
|
// src/tools/list-niches.ts
|
|
307
397
|
var LIST_NICHES_MAX_PAGE_SIZE = 50;
|
|
308
398
|
var inputSchema = {
|
|
309
|
-
currentPage:
|
|
310
|
-
pageSize:
|
|
311
|
-
searchText:
|
|
399
|
+
currentPage: z2.number().int().min(1).optional().describe("Page number, 1-indexed. Defaults to 1. Keep requesting the next page while `hasNext` is true."),
|
|
400
|
+
pageSize: z2.number().int().min(1).max(LIST_NICHES_MAX_PAGE_SIZE).optional().describe(`Items per page (max ${LIST_NICHES_MAX_PAGE_SIZE}). Defaults to 20.`),
|
|
401
|
+
searchText: z2.string().trim().min(1).max(200).optional().describe(
|
|
312
402
|
"Case-insensitive partial match on the niche label or hero keyword. Prefer this over paging through everything when the user names a niche."
|
|
313
403
|
),
|
|
314
|
-
searchAsin:
|
|
315
|
-
orderBy:
|
|
404
|
+
searchAsin: z2.string().trim().regex(/^[A-Za-z0-9]{10}$/, "must be a 10-character ASIN").optional().describe("Only niches whose competitor set contains this ASIN (10 characters, e.g. B08N5WRWNW)."),
|
|
405
|
+
orderBy: z2.enum(["lastDived", "name"]).optional().describe(
|
|
316
406
|
"Sort field: `lastDived` (by latestResearchDate, newest first by default) or `name` (by nicheLabel, A\u2192Z by default). Defaults to `lastDived`."
|
|
317
407
|
),
|
|
318
|
-
sortOrder:
|
|
408
|
+
sortOrder: z2.enum(["ASC", "DESC"]).optional().describe("Sort direction. Defaults to DESC for `lastDived` and ASC for `name`."),
|
|
409
|
+
spaces: tagFilterIdList("spaces").optional().describe(
|
|
410
|
+
"Only niches in at least one of these Spaces (ids from `list_spaces`, up to 100). Combines with `labels` and the other filters with AND."
|
|
411
|
+
),
|
|
412
|
+
labels: tagFilterIdList("labels").optional().describe(
|
|
413
|
+
"Only niches with at least one of these labels (ids from `list_labels`, up to 100). Combines with `spaces` and the other filters with AND."
|
|
414
|
+
)
|
|
319
415
|
};
|
|
320
416
|
var listNichesTool = {
|
|
321
417
|
name: "list_niches",
|
|
322
418
|
title: "List DataDive Niches",
|
|
323
|
-
description: `Use this first when the user asks about their niches, or to find a \`nicheId\` for use with \`get_niche_keywords\`, \`get_niche_competitors\`, or \`get_ranking_juice\`. Retrieves a paginated list of Niches, newest dive first. Each Niche represents a market segment or product category being tracked. Returns nicheId, heroKeyword, nicheLabel, marketplace (com/uk/de/...), and latestResearchDate per niche, plus pagination metadata (currentPage, pageSize, total, lastPage, hasNext, hasPrev). Narrow with \`searchText\` (label/keyword) or \`searchAsin\` (a competitor ASIN) instead of scanning pages;
|
|
419
|
+
description: `Use this first when the user asks about their niches, or to find a \`nicheId\` for use with \`get_niche_keywords\`, \`get_niche_competitors\`, or \`get_ranking_juice\`. Retrieves a paginated list of Niches, newest dive first. Each Niche represents a market segment or product category being tracked. Returns nicheId, heroKeyword, nicheLabel, marketplace (com/uk/de/...), and latestResearchDate per niche, plus pagination metadata (currentPage, pageSize, total, lastPage, hasNext, hasPrev). Narrow with \`searchText\` (label/keyword) or \`searchAsin\` (a competitor ASIN), \`spaces\` or \`labels\` (ids from \`list_spaces\` / \`list_labels\`) instead of scanning pages; each niche lists the ids of its \`spaces\` and \`labels\`. A page holds at most ${LIST_NICHES_MAX_PAGE_SIZE} niches, so the full list of a large account needs several calls.`,
|
|
324
420
|
inputSchema,
|
|
325
421
|
annotations: { readOnlyHint: true },
|
|
326
422
|
handler: async (args, ctx) => {
|
|
@@ -333,185 +429,421 @@ var listNichesTool = {
|
|
|
333
429
|
searchText: args.searchText,
|
|
334
430
|
searchAsin: args.searchAsin,
|
|
335
431
|
orderBy: args.orderBy,
|
|
336
|
-
sortOrder: args.sortOrder
|
|
432
|
+
sortOrder: args.sortOrder,
|
|
433
|
+
spaces: args.spaces,
|
|
434
|
+
labels: args.labels
|
|
337
435
|
}
|
|
338
436
|
);
|
|
339
437
|
}
|
|
340
438
|
};
|
|
341
439
|
|
|
342
|
-
// src/tools/
|
|
343
|
-
import { z as
|
|
440
|
+
// src/tools/list-fast-keywords.ts
|
|
441
|
+
import { z as z3 } from "zod";
|
|
344
442
|
var inputSchema2 = {
|
|
345
|
-
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
443
|
+
currentPage: z3.number().int().min(1).optional().describe("Page number, 1-indexed. Defaults to 1."),
|
|
444
|
+
pageSize: z3.number().int().min(1).max(FAST_KEYWORDS_MAX_PAGE_SIZE).optional().describe(`Items per page (max ${FAST_KEYWORDS_MAX_PAGE_SIZE}). Defaults to 20.`),
|
|
445
|
+
search: z3.string().trim().min(1).max(200).optional().describe("Case-insensitive partial match on ASIN, product title or brand."),
|
|
446
|
+
marketplace: z3.enum(SUPPORTED_MARKETPLACES).optional().describe("Only Fast Keywords runs from this marketplace.")
|
|
447
|
+
};
|
|
448
|
+
var listFastKeywordsTool = {
|
|
449
|
+
name: "list_fast_keywords",
|
|
450
|
+
title: "List Fast Keywords Runs",
|
|
451
|
+
description: "Use this first when the user asks about their Fast Keywords runs, or to find a `fastKeywordsId` for `get_fast_keywords`, `get_fast_keywords_keywords` or `refresh_fast_keywords`. Fast Keywords finds the keywords a product ranks for from just an ASIN, in minutes and for 1 Dive token, without a full niche dive. Lists the runs of the whole organization, newest request first: `fastKeywordsId`, `asin`, `parentAsin`, `marketplace`, `status` (`in-progress`, `success` or `failed`), `title`, `brand`, `price`, `keywordCount`, `totalSearchVolume`, `top10OrganicRankCount`, `rootCount`, `lastRefreshedAt` and `requestedAt`, plus pagination metadata (currentPage, pageSize, total, lastPage, hasNext, hasPrev). Narrow with `search` or `marketplace`.",
|
|
351
452
|
inputSchema: inputSchema2,
|
|
352
453
|
annotations: { readOnlyHint: true },
|
|
353
454
|
handler: async (args, ctx) => {
|
|
354
|
-
return await httpGet(
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
455
|
+
return await httpGet({ config: ctx.config, toolName: "list_fast_keywords" }, "/v1/fast-keywords", {
|
|
456
|
+
currentPage: args.currentPage,
|
|
457
|
+
pageSize: args.pageSize,
|
|
458
|
+
search: args.search,
|
|
459
|
+
marketplace: args.marketplace
|
|
460
|
+
});
|
|
358
461
|
}
|
|
359
462
|
};
|
|
360
463
|
|
|
361
|
-
// src/tools/
|
|
362
|
-
import { z as
|
|
464
|
+
// src/tools/get-fast-keywords.ts
|
|
465
|
+
import { z as z4 } from "zod";
|
|
363
466
|
var inputSchema3 = {
|
|
364
|
-
|
|
467
|
+
fastKeywordsId: z4.number().int().min(1).describe("The Fast Keywords result id, from `run_fast_keywords`, `refresh_fast_keywords` or `list_fast_keywords`.")
|
|
365
468
|
};
|
|
366
|
-
var
|
|
367
|
-
name: "
|
|
368
|
-
title: "
|
|
369
|
-
description: "Use this to
|
|
469
|
+
var getFastKeywordsTool = {
|
|
470
|
+
name: "get_fast_keywords",
|
|
471
|
+
title: "Get a Fast Keywords Result",
|
|
472
|
+
description: "Use this to poll a Fast Keywords run started with `run_fast_keywords` or `refresh_fast_keywords`, and to read its result. While `status` is `in-progress`, wait a few seconds and call again; stop at `success` or `failed`. On `failed`, `failureReason` and `failureMessage` say why (safe to show to the user). On `success` it returns the product (`title`, `brand`, `price`, `imageUrl`), its `variations`, `keywordCount`, `lastRefreshedAt`, the keyword `roots` (`root`, `aggregateSearchVolume`, `medianOrganicRank`, `keywords`) and the top 10 `competitors`. `insufficientKeywordData: true` means the run succeeded but found too few keywords to be useful. The keyword rows themselves are in `get_fast_keywords_keywords`.",
|
|
370
473
|
inputSchema: inputSchema3,
|
|
371
474
|
annotations: { readOnlyHint: true },
|
|
372
475
|
handler: async (args, ctx) => {
|
|
373
476
|
return await httpGet(
|
|
374
|
-
{ config: ctx.config, toolName: "
|
|
375
|
-
`/v1/
|
|
477
|
+
{ config: ctx.config, toolName: "get_fast_keywords" },
|
|
478
|
+
`/v1/fast-keywords/${args.fastKeywordsId}`
|
|
376
479
|
);
|
|
377
480
|
}
|
|
378
481
|
};
|
|
379
482
|
|
|
380
|
-
// src/tools/get-
|
|
381
|
-
import { z as
|
|
483
|
+
// src/tools/get-fast-keywords-keywords.ts
|
|
484
|
+
import { z as z5 } from "zod";
|
|
382
485
|
var inputSchema4 = {
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
486
|
+
fastKeywordsId: z5.number().int().min(1).describe("The Fast Keywords result id, from `run_fast_keywords` or `list_fast_keywords`."),
|
|
487
|
+
currentPage: z5.number().int().min(1).optional().describe("Page number, 1-indexed. Defaults to 1."),
|
|
488
|
+
pageSize: z5.number().int().min(1).max(FAST_KEYWORDS_MAX_PAGE_SIZE).optional().describe(`Keywords per page (max ${FAST_KEYWORDS_MAX_PAGE_SIZE}). Defaults to 20.`)
|
|
489
|
+
};
|
|
490
|
+
var getFastKeywordsKeywordsTool = {
|
|
491
|
+
name: "get_fast_keywords_keywords",
|
|
492
|
+
title: "Get the Keywords of a Fast Keywords Result",
|
|
493
|
+
description: `Use this to read the keywords of a finished Fast Keywords run (check \`status\` is \`success\` with \`get_fast_keywords\` first). Returns \`keyword\`, \`searchVolume\`, \`organicRank\` and \`sponsoredRank\` per keyword, highest search volume first, plus pagination metadata (currentPage, pageSize, total, lastPage, hasNext, hasPrev). A page holds at most ${FAST_KEYWORDS_MAX_PAGE_SIZE} keywords; keep requesting the next page while \`hasNext\` is true.`,
|
|
389
494
|
inputSchema: inputSchema4,
|
|
390
495
|
annotations: { readOnlyHint: true },
|
|
391
496
|
handler: async (args, ctx) => {
|
|
392
497
|
return await httpGet(
|
|
393
|
-
{ config: ctx.config, toolName: "
|
|
394
|
-
`/v1/
|
|
498
|
+
{ config: ctx.config, toolName: "get_fast_keywords_keywords" },
|
|
499
|
+
`/v1/fast-keywords/${args.fastKeywordsId}/keywords`,
|
|
500
|
+
{ currentPage: args.currentPage, pageSize: args.pageSize }
|
|
395
501
|
);
|
|
396
502
|
}
|
|
397
503
|
};
|
|
398
504
|
|
|
399
|
-
// src/tools/
|
|
400
|
-
import { z as
|
|
505
|
+
// src/tools/run-fast-keywords.ts
|
|
506
|
+
import { z as z6 } from "zod";
|
|
507
|
+
|
|
508
|
+
// src/tools/confirm.ts
|
|
509
|
+
function needsConfirmation(confirm, ctx) {
|
|
510
|
+
return !(confirm === true || ctx.config.autoConfirmWrites);
|
|
511
|
+
}
|
|
512
|
+
function confirmationRequired(costNote, warnings) {
|
|
513
|
+
return {
|
|
514
|
+
status: "confirmation_required",
|
|
515
|
+
message: "This action spends billable tokens and cannot be undone. Confirm the cost with the user, then call again with `confirm: true` to proceed.",
|
|
516
|
+
costNote,
|
|
517
|
+
...warnings && warnings.length > 0 ? { warnings } : {}
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
function requireConfirmation(confirm, ctx, costNote) {
|
|
521
|
+
if (!needsConfirmation(confirm, ctx)) return null;
|
|
522
|
+
return confirmationRequired(costNote);
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// src/tools/fast-keywords-run.ts
|
|
526
|
+
function isRefreshConflict(body) {
|
|
527
|
+
if (!body || typeof body !== "object") return false;
|
|
528
|
+
const b = body;
|
|
529
|
+
return b.error === "IN_PROGRESS" || b.error === "COOLDOWN";
|
|
530
|
+
}
|
|
531
|
+
function toConflictError(err) {
|
|
532
|
+
const body = err.body;
|
|
533
|
+
if (!isRefreshConflict(body)) return err;
|
|
534
|
+
const message = body.error === "COOLDOWN" && body.nextEligibleAt ? `This Fast Keywords result was refreshed recently. It can be refreshed again from ${body.nextEligibleAt}. Nothing was run or charged; read the current result with \`get_fast_keywords\`.` : body.error === "IN_PROGRESS" ? "A run for this Fast Keywords result is already in progress. Nothing was charged: poll `get_fast_keywords` until it finishes instead of calling again." : `${body.message} Nothing was run or charged.`;
|
|
535
|
+
return new ApiError("http", 409, message, body);
|
|
536
|
+
}
|
|
537
|
+
async function runFastKeywords(ctx, request, path, body, confirm, what) {
|
|
538
|
+
const post = async (payload) => {
|
|
539
|
+
try {
|
|
540
|
+
return await httpPost(request, path, payload);
|
|
541
|
+
} catch (err) {
|
|
542
|
+
throw err instanceof ApiError && err.status === 409 ? toConflictError(err) : err;
|
|
543
|
+
}
|
|
544
|
+
};
|
|
545
|
+
if (needsConfirmation(confirm, ctx)) {
|
|
546
|
+
const preview = await post({ ...body, dryRun: true });
|
|
547
|
+
if ("fastKeywordsId" in preview) return preview;
|
|
548
|
+
const { tokenCost, hasEnoughTokens } = preview;
|
|
549
|
+
return {
|
|
550
|
+
...confirmationRequired(
|
|
551
|
+
`${what} consumes ${tokenCost} Dive token${tokenCost === 1 ? "" : "s"}.` + (hasEnoughTokens ? "" : " The organization does not have enough Dive tokens left, so a confirmed call would be refused.")
|
|
552
|
+
),
|
|
553
|
+
tokenCost,
|
|
554
|
+
hasEnoughTokens
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
return await post(body);
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
// src/tools/run-fast-keywords.ts
|
|
401
561
|
var inputSchema5 = {
|
|
402
|
-
|
|
562
|
+
marketplace: z6.enum(SUPPORTED_MARKETPLACES).describe(
|
|
563
|
+
"Amazon marketplace of the ASIN. Note these are full domain suffixes: com, ca, co.uk, com.mx, in, fr, de, es, it, co.jp (e.g. UK is `co.uk`, not `uk`)."
|
|
564
|
+
),
|
|
565
|
+
asin: z6.string().min(1).describe("The ASIN to find keywords for (e.g. B08N5WRWNW)."),
|
|
566
|
+
confirm: z6.boolean().optional().describe(
|
|
567
|
+
"Leave unset on the first call. Set to true only after the user approved the Dive token cost from the `confirmation_required` response."
|
|
568
|
+
)
|
|
403
569
|
};
|
|
404
|
-
var
|
|
405
|
-
name: "
|
|
406
|
-
title: "
|
|
407
|
-
description: "Use this
|
|
570
|
+
var runFastKeywordsTool = {
|
|
571
|
+
name: "run_fast_keywords",
|
|
572
|
+
title: "Run Fast Keywords",
|
|
573
|
+
description: "Use this to find the keywords a product ranks for from just its ASIN \u2014 in minutes, without a full niche dive. \u26A0\uFE0F Spends 1 Dive token and cannot be undone. The first call checks eligibility and the Dive token balance, spends nothing and returns `confirmation_required` with `tokenCost` and `hasEnoughTokens`; call again with `confirm: true` only after the user approves. Not safe to retry: each confirmed call spends a token \u2014 if it errors or times out, check `list_fast_keywords` instead of re-calling. Returns `fastKeywordsId`, `requestId`, `status` and `estimatedCompletionDate`. Poll `get_fast_keywords` with the `fastKeywordsId` until `status` is `success` (right away when a fresh result for the same product already exists), then read the keywords with `get_fast_keywords_keywords`. To follow up with a full niche dive for the same product, pass the `requestId` to `create_niche_dive` as `fastKeywordsRequestId`. Run it again later with `refresh_fast_keywords`.",
|
|
408
574
|
inputSchema: inputSchema5,
|
|
409
|
-
|
|
575
|
+
// Additive, but it spends a Dive token irreversibly, which is what clients need to prompt on. The confirm
|
|
576
|
+
// gate enforces it server-side regardless.
|
|
577
|
+
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
410
578
|
handler: async (args, ctx) => {
|
|
411
|
-
return await
|
|
412
|
-
|
|
413
|
-
|
|
579
|
+
return await runFastKeywords(
|
|
580
|
+
ctx,
|
|
581
|
+
{ config: ctx.config, toolName: "run_fast_keywords" },
|
|
582
|
+
"/v1/fast-keywords",
|
|
583
|
+
{ marketplace: args.marketplace, asin: args.asin },
|
|
584
|
+
args.confirm,
|
|
585
|
+
"Running Fast Keywords"
|
|
414
586
|
);
|
|
415
587
|
}
|
|
416
588
|
};
|
|
417
589
|
|
|
418
|
-
// src/tools/
|
|
419
|
-
import { z as
|
|
590
|
+
// src/tools/refresh-fast-keywords.ts
|
|
591
|
+
import { z as z7 } from "zod";
|
|
420
592
|
var inputSchema6 = {
|
|
421
|
-
|
|
593
|
+
fastKeywordsId: z7.number().int().min(1).describe("The Fast Keywords result to refresh, from `run_fast_keywords` or `list_fast_keywords`."),
|
|
594
|
+
confirm: z7.boolean().optional().describe(
|
|
595
|
+
"Leave unset on the first call. Set to true only after the user approved the Dive token cost from the `confirmation_required` response."
|
|
596
|
+
)
|
|
422
597
|
};
|
|
423
|
-
var
|
|
424
|
-
name: "
|
|
425
|
-
title: "
|
|
426
|
-
description: "Use this
|
|
598
|
+
var refreshFastKeywordsTool = {
|
|
599
|
+
name: "refresh_fast_keywords",
|
|
600
|
+
title: "Refresh Fast Keywords",
|
|
601
|
+
description: "Use this to re-run an existing Fast Keywords result with current data. \u26A0\uFE0F Spends 1 Dive token and cannot be undone. The first call checks eligibility and the Dive token balance, spends nothing and returns `confirmation_required` with `tokenCost` and `hasEnoughTokens`; call again with `confirm: true` only after the user approves. A result can be refreshed once every 24 hours, and not while a run is going: then the call fails with a message that says when it is refreshable again (or to poll `get_fast_keywords`), and nothing is charged. Not safe to retry after an error or timeout \u2014 check `get_fast_keywords` instead. Returns `fastKeywordsId`, `requestId`, `status` and `estimatedCompletionDate`; poll `get_fast_keywords` until `status` is `success`.",
|
|
427
602
|
inputSchema: inputSchema6,
|
|
603
|
+
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
604
|
+
handler: async (args, ctx) => {
|
|
605
|
+
return await runFastKeywords(
|
|
606
|
+
ctx,
|
|
607
|
+
{ config: ctx.config, toolName: "refresh_fast_keywords" },
|
|
608
|
+
`/v1/fast-keywords/${args.fastKeywordsId}/refresh`,
|
|
609
|
+
{},
|
|
610
|
+
args.confirm,
|
|
611
|
+
"Refreshing Fast Keywords"
|
|
612
|
+
);
|
|
613
|
+
}
|
|
614
|
+
};
|
|
615
|
+
|
|
616
|
+
// src/tools/list-spaces.ts
|
|
617
|
+
var inputSchema7 = {};
|
|
618
|
+
var listSpacesTool = {
|
|
619
|
+
name: "list_spaces",
|
|
620
|
+
title: "List Spaces",
|
|
621
|
+
description: "Use this first when the user talks about Spaces, or to find the `id`s that `add_niche_spaces`, `remove_niche_spaces` and the `spaces` filter of `list_niches` and `list_rank_radars` take. Returns the Spaces as a tree: each node has `id`, `name`, `path` (names from the root joined with `/`), `parentId` and `children`. An MCP user sees only the Spaces they have rights on, so a Space that is missing from the tree is one they cannot use. Niches carry the ids of their Spaces in `spaces` / `labels` on `list_niches`.",
|
|
622
|
+
inputSchema: inputSchema7,
|
|
428
623
|
annotations: { readOnlyHint: true },
|
|
624
|
+
handler: async (_args, ctx) => {
|
|
625
|
+
return await httpGet({ config: ctx.config, toolName: "list_spaces" }, "/v1/spaces");
|
|
626
|
+
}
|
|
627
|
+
};
|
|
628
|
+
|
|
629
|
+
// src/tools/list-labels.ts
|
|
630
|
+
var inputSchema8 = {};
|
|
631
|
+
var listLabelsTool = {
|
|
632
|
+
name: "list_labels",
|
|
633
|
+
title: "List Labels",
|
|
634
|
+
description: "Use this first when the user talks about labels, or to find the `id`s that `add_niche_labels`, `remove_niche_labels` and the `labels` filter of `list_niches` and `list_rank_radars` take. Returns the labels as a tree: each node has `id`, `name`, `path` (names from the root joined with `/`), `parentId` and `children`. Labels carry no access rights, so every label of the organization is listed. Niches carry the ids of their labels in `spaces` / `labels` on `list_niches`.",
|
|
635
|
+
inputSchema: inputSchema8,
|
|
636
|
+
annotations: { readOnlyHint: true },
|
|
637
|
+
handler: async (_args, ctx) => {
|
|
638
|
+
return await httpGet({ config: ctx.config, toolName: "list_labels" }, "/v1/labels");
|
|
639
|
+
}
|
|
640
|
+
};
|
|
641
|
+
|
|
642
|
+
// src/tools/add-niche-spaces.ts
|
|
643
|
+
import { z as z8 } from "zod";
|
|
644
|
+
var inputSchema9 = {
|
|
645
|
+
nicheId: z8.string().min(1).describe("The niche to change. Get one from `list_niches`."),
|
|
646
|
+
spaceIds: nicheTagIdList("spaceIds").describe(
|
|
647
|
+
"The ids of the Spaces to add the niche to, 1-100, unique. Get them from `list_spaces`."
|
|
648
|
+
)
|
|
649
|
+
};
|
|
650
|
+
var addNicheSpacesTool = {
|
|
651
|
+
name: "add_niche_spaces",
|
|
652
|
+
title: "Add a Niche to Spaces",
|
|
653
|
+
description: "Use this to put a niche in one or more Spaces (ids from `list_spaces`). A Space controls who can see the niche, so adding or removing one changes access. Returns `added` and `alreadyPresent`. Undo with `remove_niche_spaces`. " + NICHE_TAG_WRITE_RULES,
|
|
654
|
+
inputSchema: inputSchema9,
|
|
655
|
+
// Like every write tool here: clients prompt on destructiveHint. Changing a Space also changes who can see the niche.
|
|
656
|
+
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
429
657
|
handler: async (args, ctx) => {
|
|
430
|
-
return await
|
|
431
|
-
{ config: ctx.config, toolName: "
|
|
432
|
-
`/v1/niches/${encodeURIComponent(args.nicheId)}/
|
|
658
|
+
return await httpPost(
|
|
659
|
+
{ config: ctx.config, toolName: "add_niche_spaces" },
|
|
660
|
+
`/v1/niches/${encodeURIComponent(args.nicheId)}/spaces`,
|
|
661
|
+
{ spaceIds: args.spaceIds }
|
|
433
662
|
);
|
|
434
663
|
}
|
|
435
664
|
};
|
|
436
665
|
|
|
437
|
-
// src/tools/
|
|
438
|
-
import { z as
|
|
666
|
+
// src/tools/remove-niche-spaces.ts
|
|
667
|
+
import { z as z9 } from "zod";
|
|
668
|
+
var inputSchema10 = {
|
|
669
|
+
nicheId: z9.string().min(1).describe("The niche to change. Get one from `list_niches`."),
|
|
670
|
+
spaceIds: nicheTagIdList("spaceIds").describe(
|
|
671
|
+
"The ids of the Spaces to take the niche out of, 1-100, unique. Get them from `list_spaces`."
|
|
672
|
+
)
|
|
673
|
+
};
|
|
674
|
+
var removeNicheSpacesTool = {
|
|
675
|
+
name: "remove_niche_spaces",
|
|
676
|
+
title: "Remove a Niche from Spaces",
|
|
677
|
+
description: "Use this to take a niche out of one or more Spaces (ids from `list_spaces`). A Space controls who can see the niche, so adding or removing one changes access. Returns `removed` and `notPresent`. Undo with `add_niche_spaces`. " + NICHE_TAG_WRITE_RULES,
|
|
678
|
+
inputSchema: inputSchema10,
|
|
679
|
+
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
680
|
+
handler: async (args, ctx) => {
|
|
681
|
+
return await httpPost(
|
|
682
|
+
{ config: ctx.config, toolName: "remove_niche_spaces" },
|
|
683
|
+
`/v1/niches/${encodeURIComponent(args.nicheId)}/spaces/remove`,
|
|
684
|
+
{ spaceIds: args.spaceIds }
|
|
685
|
+
);
|
|
686
|
+
}
|
|
687
|
+
};
|
|
439
688
|
|
|
440
|
-
// src/
|
|
441
|
-
|
|
442
|
-
var
|
|
443
|
-
"
|
|
444
|
-
"
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
"
|
|
450
|
-
"
|
|
451
|
-
"
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
"
|
|
467
|
-
"
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
"
|
|
473
|
-
"
|
|
474
|
-
"
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
var
|
|
492
|
-
|
|
493
|
-
"
|
|
494
|
-
"
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
689
|
+
// src/tools/add-niche-labels.ts
|
|
690
|
+
import { z as z10 } from "zod";
|
|
691
|
+
var inputSchema11 = {
|
|
692
|
+
nicheId: z10.string().min(1).describe("The niche to change. Get one from `list_niches`."),
|
|
693
|
+
labelIds: nicheTagIdList("labelIds").describe(
|
|
694
|
+
"The ids of the labels to add the niche to, 1-100, unique. Get them from `list_labels`."
|
|
695
|
+
)
|
|
696
|
+
};
|
|
697
|
+
var addNicheLabelsTool = {
|
|
698
|
+
name: "add_niche_labels",
|
|
699
|
+
title: "Add Labels to a Niche",
|
|
700
|
+
description: "Use this to put a niche in one or more labels (ids from `list_labels`). Returns `added` and `alreadyPresent`. Undo with `remove_niche_labels`. " + NICHE_TAG_WRITE_RULES,
|
|
701
|
+
inputSchema: inputSchema11,
|
|
702
|
+
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
703
|
+
handler: async (args, ctx) => {
|
|
704
|
+
return await httpPost(
|
|
705
|
+
{ config: ctx.config, toolName: "add_niche_labels" },
|
|
706
|
+
`/v1/niches/${encodeURIComponent(args.nicheId)}/labels`,
|
|
707
|
+
{ labelIds: args.labelIds }
|
|
708
|
+
);
|
|
709
|
+
}
|
|
710
|
+
};
|
|
711
|
+
|
|
712
|
+
// src/tools/remove-niche-labels.ts
|
|
713
|
+
import { z as z11 } from "zod";
|
|
714
|
+
var inputSchema12 = {
|
|
715
|
+
nicheId: z11.string().min(1).describe("The niche to change. Get one from `list_niches`."),
|
|
716
|
+
labelIds: nicheTagIdList("labelIds").describe(
|
|
717
|
+
"The ids of the labels to take the niche out of, 1-100, unique. Get them from `list_labels`."
|
|
718
|
+
)
|
|
719
|
+
};
|
|
720
|
+
var removeNicheLabelsTool = {
|
|
721
|
+
name: "remove_niche_labels",
|
|
722
|
+
title: "Remove Labels from a Niche",
|
|
723
|
+
description: "Use this to take a niche out of one or more labels (ids from `list_labels`). Returns `removed` and `notPresent`. Undo with `add_niche_labels`. " + NICHE_TAG_WRITE_RULES,
|
|
724
|
+
inputSchema: inputSchema12,
|
|
725
|
+
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
726
|
+
handler: async (args, ctx) => {
|
|
727
|
+
return await httpPost(
|
|
728
|
+
{ config: ctx.config, toolName: "remove_niche_labels" },
|
|
729
|
+
`/v1/niches/${encodeURIComponent(args.nicheId)}/labels/remove`,
|
|
730
|
+
{ labelIds: args.labelIds }
|
|
731
|
+
);
|
|
732
|
+
}
|
|
733
|
+
};
|
|
734
|
+
|
|
735
|
+
// src/tools/get-niche-keywords.ts
|
|
736
|
+
import { z as z12 } from "zod";
|
|
737
|
+
var inputSchema13 = {
|
|
738
|
+
nicheId: z12.string().min(1).describe("The unique identifier of the Niche (from `list_niches`).")
|
|
739
|
+
};
|
|
740
|
+
var getNicheKeywordsTool = {
|
|
741
|
+
name: "get_niche_keywords",
|
|
742
|
+
title: "Get Master Keyword List for a Niche",
|
|
743
|
+
description: 'Use this when the user asks about keywords, search terms, or search volume for a niche. Retrieves the master keyword list for the specified Niche \u2014 the relevant search terms monitored for ranking and performance metrics. For each keyword, returns search volume, relevancy (numeric score or "Outlier"), and competitor ASIN organic ranks (asinRanks: { ASIN -> rank | null }), plus `origin`: `mkl` (found by the dive), `manual` (added by hand with `add_niche_keywords`), `outlier` / `residue` (moved in from those lists). Only `manual`, `outlier` and `residue` keywords can be taken out with `remove_niche_keywords`. Keywords hidden by the niche\'s keyword exclusions are not listed; see `list_keyword_exclusions`. Also returns the latestResearchDate.',
|
|
744
|
+
inputSchema: inputSchema13,
|
|
745
|
+
annotations: { readOnlyHint: true },
|
|
746
|
+
handler: async (args, ctx) => {
|
|
747
|
+
return await httpGet(
|
|
748
|
+
{ config: ctx.config, toolName: "get_niche_keywords" },
|
|
749
|
+
`/v1/niches/${encodeURIComponent(args.nicheId)}/keywords`
|
|
750
|
+
);
|
|
751
|
+
}
|
|
752
|
+
};
|
|
753
|
+
|
|
754
|
+
// src/tools/list-keyword-exclusions.ts
|
|
755
|
+
import { z as z13 } from "zod";
|
|
756
|
+
var inputSchema14 = {
|
|
757
|
+
nicheId: z13.string().min(1).describe("The unique identifier of the Niche (from `list_niches`).")
|
|
758
|
+
};
|
|
759
|
+
var listKeywordExclusionsTool = {
|
|
760
|
+
name: "list_keyword_exclusions",
|
|
761
|
+
title: "List a Niche's Keyword Exclusions",
|
|
762
|
+
description: "Use this to see which keywords are hidden from a niche's Master Keyword List, e.g. when a keyword the user expects is missing from `get_niche_keywords`. Returns the same lists the DataDive web app shows, oldest first: `exact` \u2014 each entry hides the one keyword that matches it exactly, with that keyword's `searchVolume` (0 when the niche has no such keyword); `phrase` \u2014 each entry hides every keyword that contains the phrase, with `excludedKeywordsCount` and the total `searchVolume` of the keywords it hides (not counting ones already excluded exactly). Exclusions hide keywords wherever they come from: the dive, the Outliers and Residue lists, and hand-added ones. Change them with `add_keyword_exclusions` and `remove_keyword_exclusions`.",
|
|
763
|
+
inputSchema: inputSchema14,
|
|
764
|
+
annotations: { readOnlyHint: true },
|
|
765
|
+
handler: async (args, ctx) => {
|
|
766
|
+
return await httpGet(
|
|
767
|
+
{ config: ctx.config, toolName: "list_keyword_exclusions" },
|
|
768
|
+
`/v1/niches/${encodeURIComponent(args.nicheId)}/keyword-exclusions`
|
|
769
|
+
);
|
|
770
|
+
}
|
|
771
|
+
};
|
|
772
|
+
|
|
773
|
+
// src/tools/get-niche-roots.ts
|
|
774
|
+
import { z as z14 } from "zod";
|
|
775
|
+
var inputSchema15 = {
|
|
776
|
+
nicheId: z14.string().min(1).describe("The unique identifier of the Niche (from `list_niches`).")
|
|
777
|
+
};
|
|
778
|
+
var getNicheRootsTool = {
|
|
779
|
+
name: "get_niche_roots",
|
|
780
|
+
title: "Get Keyword Roots for a Niche",
|
|
781
|
+
description: "Use this when the user wants to find the highest-impact words across a niche's keywords, asks about 'keyword roots', or wants to prioritize terms for a listing. Retrieves the keyword lexical roots for the Niche \u2014 individual words or word-combinations extracted from the Master Keyword List (e.g. 'bluetooth headphones' yields roots 'bluetooth', 'headphones', 'bluetooth headphones'). Returns `roots` and `normalizedRoots` tables, each item with root, frequency (how many keywords contain it), broadSearchVolume (summed search volume of those keywords), and broadSearchVolumeRatio (0-1, relative to the top root), plus the per-keyword breakdowns and latestResearchDate.",
|
|
782
|
+
inputSchema: inputSchema15,
|
|
783
|
+
annotations: { readOnlyHint: true },
|
|
784
|
+
handler: async (args, ctx) => {
|
|
785
|
+
return await httpGet(
|
|
786
|
+
{ config: ctx.config, toolName: "get_niche_roots" },
|
|
787
|
+
`/v1/niches/${encodeURIComponent(args.nicheId)}/roots`
|
|
788
|
+
);
|
|
789
|
+
}
|
|
790
|
+
};
|
|
791
|
+
|
|
792
|
+
// src/tools/get-niche-competitors.ts
|
|
793
|
+
import { z as z15 } from "zod";
|
|
794
|
+
var inputSchema16 = {
|
|
795
|
+
nicheId: z15.string().min(1).describe("The unique identifier of the Niche (from `list_niches`).")
|
|
796
|
+
};
|
|
797
|
+
var getNicheCompetitorsTool = {
|
|
798
|
+
name: "get_niche_competitors",
|
|
799
|
+
title: "Get Competitors and Niche Statistics",
|
|
800
|
+
description: "Use this for competitor ASINs, product titles, BSR, sales/revenue benchmarks, or niche opportunity scoring. Retrieves the list of Competitors within the specified Niche along with Niche statistics. For the Niche: keyword statistics, opportunity evaluation, benchmark median values, and overall competitor strength assessment. For each Competitor: ASIN, product title, BSR, category and full category tree, sales, revenue, ratings, reviews, price, number of variations, image URL, total Ranking Juice, and ranking data. BSR supports ranking Competitors by sales position within mentioned category. For the Ranking Juice breakdown by title, bullets and description, use `get_ranking_juice`.",
|
|
801
|
+
inputSchema: inputSchema16,
|
|
802
|
+
annotations: { readOnlyHint: true },
|
|
803
|
+
handler: async (args, ctx) => {
|
|
804
|
+
return await httpGet(
|
|
805
|
+
{ config: ctx.config, toolName: "get_niche_competitors" },
|
|
806
|
+
`/v1/niches/${encodeURIComponent(args.nicheId)}/competitors`
|
|
807
|
+
);
|
|
808
|
+
}
|
|
809
|
+
};
|
|
810
|
+
|
|
811
|
+
// src/tools/get-ranking-juice.ts
|
|
812
|
+
import { z as z16 } from "zod";
|
|
813
|
+
var inputSchema17 = {
|
|
814
|
+
nicheId: z16.string().min(1).describe("The unique identifier of the Niche (from `list_niches`).")
|
|
815
|
+
};
|
|
816
|
+
var getRankingJuiceTool = {
|
|
817
|
+
name: "get_ranking_juice",
|
|
818
|
+
title: "Get Ranking Juices for a Niche",
|
|
819
|
+
description: "Use this when the user asks 'how do I rank higher', 'what's my ranking juice', or wants listing-optimization guidance. Retrieves the Ranking Juices for each Competitor within the specified Niche. Ranking Juice is DataDive's proprietary metric identifying the key factors driving search-ranking success for each Competitor's listing. Returns currentListing and optimizedListing breakdowns (rankingJuice + per-property contributions: title, bullets, description) plus per-competitor listing scores and the latestResearchDate.",
|
|
820
|
+
inputSchema: inputSchema17,
|
|
821
|
+
annotations: { readOnlyHint: true },
|
|
822
|
+
handler: async (args, ctx) => {
|
|
823
|
+
return await httpGet(
|
|
824
|
+
{ config: ctx.config, toolName: "get_ranking_juice" },
|
|
825
|
+
`/v1/niches/${encodeURIComponent(args.nicheId)}/ranking-juices`
|
|
826
|
+
);
|
|
827
|
+
}
|
|
828
|
+
};
|
|
504
829
|
|
|
505
830
|
// src/tools/list-rank-radars.ts
|
|
831
|
+
import { z as z17 } from "zod";
|
|
506
832
|
var LIST_RANK_RADARS_MAX_PAGE_SIZE = 50;
|
|
507
|
-
var
|
|
508
|
-
currentPage:
|
|
509
|
-
pageSize:
|
|
510
|
-
nicheId:
|
|
511
|
-
status:
|
|
833
|
+
var inputSchema18 = {
|
|
834
|
+
currentPage: z17.number().int().min(1).optional().describe("Page number, 1-indexed. Defaults to 1."),
|
|
835
|
+
pageSize: z17.number().int().min(1).max(LIST_RANK_RADARS_MAX_PAGE_SIZE).optional().describe(`Items per page (max ${LIST_RANK_RADARS_MAX_PAGE_SIZE}). Defaults to 20.`),
|
|
836
|
+
nicheId: z17.string().min(1).optional().describe("Filter Rank Radars by Niche identifier. Use after `list_niches`."),
|
|
837
|
+
status: z17.enum(RANK_RADAR_STATUSES).optional().describe(
|
|
512
838
|
"Which Rank Radars to return. ACTIVE (the default) are tracking; PAUSED have been stopped but keep their history and can be resumed; ARCHIVED have been deleted; ALL means active and paused together. Archived Rank Radars are returned only by an explicit ARCHIVED."
|
|
513
839
|
),
|
|
514
|
-
searchText:
|
|
840
|
+
searchText: z17.string().optional().describe("Filter Rank Radars by ASIN or product title."),
|
|
841
|
+
spaces: tagFilterIdList("spaces").optional().describe(
|
|
842
|
+
"Only Rank Radars whose niche is in at least one of these Spaces (ids from `list_spaces`, up to 100). Combines with `labels` and the other filters with AND."
|
|
843
|
+
),
|
|
844
|
+
labels: tagFilterIdList("labels").optional().describe(
|
|
845
|
+
"Only Rank Radars whose niche has at least one of these labels (ids from `list_labels`, up to 100). Combines with `spaces` and the other filters with AND."
|
|
846
|
+
)
|
|
515
847
|
};
|
|
516
848
|
function minimize(item) {
|
|
517
849
|
const { asin, ...rest } = item;
|
|
@@ -520,8 +852,8 @@ function minimize(item) {
|
|
|
520
852
|
var listRankRadarsTool = {
|
|
521
853
|
name: "list_rank_radars",
|
|
522
854
|
title: "List Rank Radars",
|
|
523
|
-
description: `Use this to find a \`rankRadarId\` before calling \`get_rank_radar_data\`. Filter by \`nicheId\` if the user has already identified a niche. Retrieves a paginated list of Rank Radars \u2014 keyword-rank trackers monitoring organic and sponsored positions for specific ASINs over time. Each item includes id, status (ACTIVE, PAUSED or ARCHIVED), asin, marketplace, keywordCount, title, imageUrl, and summary metrics: top10KW, top10SV, top50KW, top50SV. Supports pagination metadata (currentPage, pageSize, total, lastPage, hasNext, hasPrev); a page holds at most ${LIST_RANK_RADARS_MAX_PAGE_SIZE} Rank Radars, so a large account needs several calls.`,
|
|
524
|
-
inputSchema:
|
|
855
|
+
description: `Use this to find a \`rankRadarId\` before calling \`get_rank_radar_data\`. Filter by \`nicheId\` if the user has already identified a niche, or by \`spaces\` / \`labels\` (ids from \`list_spaces\` / \`list_labels\`), which match through the Rank Radar's niche. Retrieves a paginated list of Rank Radars \u2014 keyword-rank trackers monitoring organic and sponsored positions for specific ASINs over time. Each item includes id, status (ACTIVE, PAUSED or ARCHIVED), asin, marketplace, keywordCount, title, imageUrl, and summary metrics: top10KW, top10SV, top50KW, top50SV. Supports pagination metadata (currentPage, pageSize, total, lastPage, hasNext, hasPrev); a page holds at most ${LIST_RANK_RADARS_MAX_PAGE_SIZE} Rank Radars, so a large account needs several calls.`,
|
|
856
|
+
inputSchema: inputSchema18,
|
|
525
857
|
annotations: { readOnlyHint: true },
|
|
526
858
|
handler: async (args, ctx) => {
|
|
527
859
|
const page = await httpGet(
|
|
@@ -532,7 +864,9 @@ var listRankRadarsTool = {
|
|
|
532
864
|
pageSize: args.pageSize,
|
|
533
865
|
nicheId: args.nicheId,
|
|
534
866
|
status: args.status,
|
|
535
|
-
searchText: args.searchText
|
|
867
|
+
searchText: args.searchText,
|
|
868
|
+
spaces: args.spaces,
|
|
869
|
+
labels: args.labels
|
|
536
870
|
}
|
|
537
871
|
);
|
|
538
872
|
return { ...page, data: (page.data ?? []).map(minimize) };
|
|
@@ -540,18 +874,18 @@ var listRankRadarsTool = {
|
|
|
540
874
|
};
|
|
541
875
|
|
|
542
876
|
// src/tools/rank-radar-keyword-query.ts
|
|
543
|
-
import { z as
|
|
877
|
+
import { z as z18 } from "zod";
|
|
544
878
|
var ISO_DATE = /^\d{4}-\d{2}-\d{2}$/;
|
|
545
879
|
var RANK_RADAR_KEYWORDS_MAX_PAGE_SIZE = 100;
|
|
546
880
|
var RANK_RADAR_MAX_DATE_RANGE_DAYS = 90;
|
|
547
881
|
var rankRadarKeywordQueryInputSchema = {
|
|
548
|
-
rankRadarId:
|
|
549
|
-
startDate:
|
|
550
|
-
endDate:
|
|
882
|
+
rankRadarId: z18.string().uuid().describe("The Rank Radar UUID (from `list_rank_radars`)."),
|
|
883
|
+
startDate: z18.string().regex(ISO_DATE, "startDate must be in yyyy-mm-dd format").describe("Start date of the range, yyyy-mm-dd (e.g. 2024-03-26)."),
|
|
884
|
+
endDate: z18.string().regex(ISO_DATE, "endDate must be in yyyy-mm-dd format").describe(
|
|
551
885
|
`End date of the range, yyyy-mm-dd (e.g. 2024-04-26). Must be on or after startDate, and at most ${RANK_RADAR_MAX_DATE_RANGE_DAYS} days after it.`
|
|
552
886
|
),
|
|
553
|
-
currentPage:
|
|
554
|
-
pageSize:
|
|
887
|
+
currentPage: z18.number().int().min(1).optional().describe("Page of keywords, 1-indexed. Defaults to 1."),
|
|
888
|
+
pageSize: z18.number().int().min(1).max(RANK_RADAR_KEYWORDS_MAX_PAGE_SIZE).optional().describe(`Keywords per page (max ${RANK_RADAR_KEYWORDS_MAX_PAGE_SIZE}). Defaults to 20.`)
|
|
555
889
|
};
|
|
556
890
|
var RANK_RADAR_KEYWORD_PAGING_NOTE = `Results are paged by keyword: \`data\` holds one page, and the response carries currentPage, pageSize, total, lastPage, hasNext and hasPrev. \`total\` is the Rank Radar's active keyword count; to read every keyword, call again with \`currentPage\` + 1 while \`hasNext\` is true, using \`pageSize\` ${RANK_RADAR_KEYWORDS_MAX_PAGE_SIZE} for large Rank Radars. Paused keywords are not included.`;
|
|
557
891
|
function rankRadarPath(rankRadarId, suffix = "") {
|
|
@@ -567,7 +901,7 @@ function rankRadarKeywordQuery(args) {
|
|
|
567
901
|
}
|
|
568
902
|
|
|
569
903
|
// src/tools/get-rank-radar-data.ts
|
|
570
|
-
var
|
|
904
|
+
var inputSchema19 = rankRadarKeywordQueryInputSchema;
|
|
571
905
|
async function readAllKeywords(args, ctx) {
|
|
572
906
|
const request = { config: ctx.config, toolName: "get_rank_radar_data" };
|
|
573
907
|
const path = rankRadarPath(args.rankRadarId);
|
|
@@ -597,7 +931,7 @@ var getRankRadarDataTool = {
|
|
|
597
931
|
name: "get_rank_radar_data",
|
|
598
932
|
title: "Get Keyword Rankings for a Rank Radar",
|
|
599
933
|
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\`.`,
|
|
600
|
-
inputSchema:
|
|
934
|
+
inputSchema: inputSchema19,
|
|
601
935
|
annotations: { readOnlyHint: true },
|
|
602
936
|
handler: async (args, ctx) => {
|
|
603
937
|
if (args.currentPage === void 0 && args.pageSize === void 0) {
|
|
@@ -612,12 +946,12 @@ var getRankRadarDataTool = {
|
|
|
612
946
|
};
|
|
613
947
|
|
|
614
948
|
// src/tools/get-rank-radar-sqp-data.ts
|
|
615
|
-
var
|
|
949
|
+
var inputSchema20 = rankRadarKeywordQueryInputSchema;
|
|
616
950
|
var getRankRadarSqpDataTool = {
|
|
617
951
|
name: "get_rank_radar_sqp_data",
|
|
618
952
|
title: "Get Search Query Performance for a Rank Radar",
|
|
619
953
|
description: `Use this when the user asks how shoppers search, click, add to cart and buy for the keywords of a Rank Radar \u2014 Amazon's Search Query Performance (SQP) data, from Brand Analytics. Requires startDate and endDate (yyyy-mm-dd), at most ${RANK_RADAR_MAX_DATE_RANGE_DAYS} days apart; 30 days or less keeps it fast. Each keyword's metrics are aggregated over the range: searchQueryVolume and searchQueryScore; impressions, clicks, cart adds and purchases, each as the total across all sellers (\`*TotalCount\`), this ASIN family's count (\`*AsinCount\`) and its share (\`*AsinShare\`); click, cart-add and purchase rates; and ctr/cvr for the market (\`*Total\`) and for this ASIN family (\`*Asin\`). \`numberOfDaysWithData\` says how many days of the range had SQP data; metrics are null when there is none, which is normal for low-volume keywords, recent dates (Amazon publishes SQP with a delay) and sellers without Brand Analytics. ${RANK_RADAR_KEYWORD_PAGING_NOTE} Each call counts toward API usage and is rate limited (about 60 requests/minute). Use after \`list_rank_radars\` to discover a \`rankRadarId\`.`,
|
|
620
|
-
inputSchema:
|
|
954
|
+
inputSchema: inputSchema20,
|
|
621
955
|
annotations: { readOnlyHint: true },
|
|
622
956
|
handler: async (args, ctx) => {
|
|
623
957
|
return await httpGet(
|
|
@@ -629,10 +963,10 @@ var getRankRadarSqpDataTool = {
|
|
|
629
963
|
};
|
|
630
964
|
|
|
631
965
|
// src/tools/get-rank-radar-ppc-data.ts
|
|
632
|
-
import { z as
|
|
633
|
-
var
|
|
966
|
+
import { z as z19 } from "zod";
|
|
967
|
+
var inputSchema21 = {
|
|
634
968
|
...rankRadarKeywordQueryInputSchema,
|
|
635
|
-
includeCampaigns:
|
|
969
|
+
includeCampaigns: z19.boolean().optional().describe(
|
|
636
970
|
"When true, each keyword also carries `campaigns`: the per-campaign/ad-group breakdown (campaign and ad group name, match type, targeting, spend, sales, clicks, impressions, orders, CPC, CTR, CVR, ACOS). Defaults to false; leave it off unless the user asks which campaigns drive a keyword."
|
|
637
971
|
)
|
|
638
972
|
};
|
|
@@ -640,7 +974,7 @@ var getRankRadarPpcDataTool = {
|
|
|
640
974
|
name: "get_rank_radar_ppc_data",
|
|
641
975
|
title: "Get PPC Data for a Rank Radar",
|
|
642
976
|
description: `Use this when the user asks about advertising (Sponsored Products) performance for the keywords of a Rank Radar. Requires startDate and endDate (yyyy-mm-dd), at most ${RANK_RADAR_MAX_DATE_RANGE_DAYS} days apart; 30 days or less keeps it fast. Each keyword's metrics are aggregated over the range: sponsoredRank (median; 101 means not in the top 100 sponsored results), impressionRank and impressionRankShare; how many exact, phrase, broad and auto campaigns target it; organicSales, ppcSales, ppcSpend, costPerClicks, clickThroughRate, conversionRate, totalClicks, totalImpressions, totalOrders and acos. Metrics are null or 0 when the seller runs no ads on a keyword. Set \`includeCampaigns\` for the per-campaign breakdown. ${RANK_RADAR_KEYWORD_PAGING_NOTE} Each call counts toward API usage and is rate limited (about 60 requests/minute). For the seller's campaigns as a whole, use \`list_ppc_campaigns\`. Use after \`list_rank_radars\` to discover a \`rankRadarId\`.`,
|
|
643
|
-
inputSchema:
|
|
977
|
+
inputSchema: inputSchema21,
|
|
644
978
|
annotations: { readOnlyHint: true },
|
|
645
979
|
handler: async (args, ctx) => {
|
|
646
980
|
return await httpGet(
|
|
@@ -652,32 +986,13 @@ var getRankRadarPpcDataTool = {
|
|
|
652
986
|
};
|
|
653
987
|
|
|
654
988
|
// src/tools/create-rank-radar.ts
|
|
655
|
-
import { z as
|
|
656
|
-
|
|
657
|
-
// src/tools/confirm.ts
|
|
658
|
-
function needsConfirmation(confirm, ctx) {
|
|
659
|
-
return !(confirm === true || ctx.config.autoConfirmWrites);
|
|
660
|
-
}
|
|
661
|
-
function confirmationRequired(costNote, warnings) {
|
|
662
|
-
return {
|
|
663
|
-
status: "confirmation_required",
|
|
664
|
-
message: "This action spends billable tokens and cannot be undone. Confirm the cost with the user, then call again with `confirm: true` to proceed.",
|
|
665
|
-
costNote,
|
|
666
|
-
...warnings && warnings.length > 0 ? { warnings } : {}
|
|
667
|
-
};
|
|
668
|
-
}
|
|
669
|
-
function requireConfirmation(confirm, ctx, costNote) {
|
|
670
|
-
if (!needsConfirmation(confirm, ctx)) return null;
|
|
671
|
-
return confirmationRequired(costNote);
|
|
672
|
-
}
|
|
673
|
-
|
|
674
|
-
// src/tools/create-rank-radar.ts
|
|
989
|
+
import { z as z20 } from "zod";
|
|
675
990
|
var PATH = "/v1/niches/rank-radars";
|
|
676
|
-
var
|
|
677
|
-
asin:
|
|
678
|
-
numberOfKeywords:
|
|
679
|
-
nicheId:
|
|
680
|
-
confirm:
|
|
991
|
+
var inputSchema22 = {
|
|
992
|
+
asin: z20.string().min(1).describe("The ASIN to track keyword rankings for."),
|
|
993
|
+
numberOfKeywords: z20.number().int().min(1).describe("How many keywords to track (minimum 1). More keywords = broader tracking but more Search Term tokens."),
|
|
994
|
+
nicheId: z20.string().min(1).describe("The niche this Rank Radar belongs to. Get one from `list_niches`."),
|
|
995
|
+
confirm: z20.boolean().optional().describe(
|
|
681
996
|
"Must be true to proceed \u2014 creating a Rank Radar spends Search Term tokens. Confirm the cost with the user first."
|
|
682
997
|
)
|
|
683
998
|
};
|
|
@@ -685,7 +1000,7 @@ var createRankRadarTool = {
|
|
|
685
1000
|
name: "create_rank_radar",
|
|
686
1001
|
title: "Create a Rank Radar",
|
|
687
1002
|
description: "Use this to start tracking organic and sponsored keyword rankings for an ASIN within a niche. \u26A0\uFE0F Spends Search Term tokens (cost scales with `numberOfKeywords`) and cannot be undone \u2014 set `confirm: true` only after the user approves the cost. Not safe to retry: each call spends tokens again and creates a separate Rank Radar \u2014 if a call errors or times out, check `list_rank_radars` instead of re-calling. The first call previews the creation and may return `warnings` alongside the cost note \u2014 for example that this product family is already tracked by another Rank Radar. Show every warning `message` to the user verbatim, before asking them to confirm; do not summarise it or decide on their behalf. A warning never blocks creation. Returns the new `rankRadarId`; read its data later with `get_rank_radar_data`. Requires `asin`, `numberOfKeywords` (min 1), and a `nicheId` from `list_niches`.",
|
|
688
|
-
inputSchema:
|
|
1003
|
+
inputSchema: inputSchema22,
|
|
689
1004
|
// destructiveHint is technically "additive" here (this creates a new Rank Radar, it
|
|
690
1005
|
// doesn't destroy data), but we flag it destructive so clients prompt before the
|
|
691
1006
|
// irreversible token spend. The confirm gate enforces this server-side regardless.
|
|
@@ -710,20 +1025,23 @@ var createRankRadarTool = {
|
|
|
710
1025
|
};
|
|
711
1026
|
|
|
712
1027
|
// src/tools/create-niche-dive.ts
|
|
713
|
-
import { z as
|
|
714
|
-
var
|
|
715
|
-
marketplace:
|
|
1028
|
+
import { z as z21 } from "zod";
|
|
1029
|
+
var inputSchema23 = {
|
|
1030
|
+
marketplace: z21.enum(SUPPORTED_MARKETPLACES).describe(
|
|
716
1031
|
"Amazon marketplace of the seed ASIN. Note these are full domain suffixes: com, ca, co.uk, com.mx, in, fr, de, es, it, co.jp (e.g. UK is `co.uk`, not `uk`)."
|
|
717
1032
|
),
|
|
718
|
-
asin:
|
|
719
|
-
numberOfCompetitors:
|
|
720
|
-
|
|
1033
|
+
asin: z21.string().min(1).describe("The seed ASIN to start the dive from (e.g. B08N5WRWNW). Competitors are built around it."),
|
|
1034
|
+
numberOfCompetitors: z21.number().int().min(2).describe("How many competitors to analyze (minimum 2). More competitors = deeper niche but more dive tokens."),
|
|
1035
|
+
fastKeywordsRequestId: z21.number().int().min(1).optional().describe(
|
|
1036
|
+
"Links the dive to a Fast Keywords run for the same product: the `requestId` returned by `run_fast_keywords` or `refresh_fast_keywords`. Leave unset for a dive that follows no Fast Keywords run."
|
|
1037
|
+
),
|
|
1038
|
+
confirm: z21.boolean().optional().describe("Must be true to proceed \u2014 creating a dive spends dive tokens. Confirm the cost with the user first.")
|
|
721
1039
|
};
|
|
722
1040
|
var createNicheDiveTool = {
|
|
723
1041
|
name: "create_niche_dive",
|
|
724
1042
|
title: "Create a Niche Dive",
|
|
725
|
-
description: "Use this to start new niche research from a seed ASIN. \u26A0\uFE0F Spends dive tokens (cost scales with `numberOfCompetitors`) and cannot be undone \u2014 set `confirm: true` only after the user approves the cost. Not safe to retry: each call spends tokens again and starts a separate dive \u2014 if a call errors or times out, poll `get_dive_status` (or check `list_niches`) instead of re-calling. The dive runs asynchronously: this returns immediately with a `diveId` and an `estimatedCompletionDate`. Poll `get_dive_status` with that `diveId` until it reports `success` (which carries the new `nicheId` for use with `list_niches`, `get_niche_keywords`, etc.). Requires `marketplace`, `asin`, and `numberOfCompetitors` (min 2).",
|
|
726
|
-
inputSchema:
|
|
1043
|
+
description: "Use this to start new niche research from a seed ASIN. \u26A0\uFE0F Spends dive tokens (cost scales with `numberOfCompetitors`) and cannot be undone \u2014 set `confirm: true` only after the user approves the cost. Not safe to retry: each call spends tokens again and starts a separate dive \u2014 if a call errors or times out, poll `get_dive_status` (or check `list_niches`) instead of re-calling. The dive runs asynchronously: this returns immediately with a `diveId` and an `estimatedCompletionDate`. Poll `get_dive_status` with that `diveId` until it reports `success` (which carries the new `nicheId` for use with `list_niches`, `get_niche_keywords`, etc.). Requires `marketplace`, `asin`, and `numberOfCompetitors` (min 2). After a Fast Keywords run for the same product, pass its `requestId` as `fastKeywordsRequestId` to link the dive to it.",
|
|
1044
|
+
inputSchema: inputSchema23,
|
|
727
1045
|
// destructiveHint is technically "additive" here (a dive creates a new niche, it
|
|
728
1046
|
// doesn't destroy data), but we flag it destructive so clients prompt before the
|
|
729
1047
|
// irreversible token spend. The confirm gate enforces this server-side regardless.
|
|
@@ -741,27 +1059,29 @@ var createNicheDiveTool = {
|
|
|
741
1059
|
{
|
|
742
1060
|
marketplace: args.marketplace,
|
|
743
1061
|
asin: args.asin,
|
|
744
|
-
numberOfCompetitors: args.numberOfCompetitors
|
|
1062
|
+
numberOfCompetitors: args.numberOfCompetitors,
|
|
1063
|
+
// JSON.stringify drops it when unset, so a dive without a Fast Keywords run sends the old body.
|
|
1064
|
+
fastKeywordsRequestId: args.fastKeywordsRequestId
|
|
745
1065
|
}
|
|
746
1066
|
);
|
|
747
1067
|
}
|
|
748
1068
|
};
|
|
749
1069
|
|
|
750
1070
|
// src/tools/create-niche-dive-from-competitors-list.ts
|
|
751
|
-
import { z as
|
|
1071
|
+
import { z as z22 } from "zod";
|
|
752
1072
|
var DIVE_FROM_COMPETITORS_MIN_ASINS = 2;
|
|
753
1073
|
var DIVE_FROM_COMPETITORS_MAX_ASINS = 200;
|
|
754
|
-
var normalizedAsins =
|
|
1074
|
+
var normalizedAsins = z22.array(z22.string().trim().min(1).toUpperCase()).min(DIVE_FROM_COMPETITORS_MIN_ASINS).max(DIVE_FROM_COMPETITORS_MAX_ASINS).refine((asins) => new Set(asins).size === asins.length, {
|
|
755
1075
|
message: "asins must not contain the same ASIN twice"
|
|
756
1076
|
});
|
|
757
|
-
var
|
|
758
|
-
marketplace:
|
|
1077
|
+
var inputSchema24 = {
|
|
1078
|
+
marketplace: z22.enum(SUPPORTED_MARKETPLACES).describe(
|
|
759
1079
|
"Amazon marketplace where the ASINs are listed. Full domain suffixes: com, ca, co.uk, com.mx, in, fr, de, es, it, co.jp (e.g. UK is `co.uk`, not `uk`)."
|
|
760
1080
|
),
|
|
761
1081
|
asins: normalizedAsins.describe(
|
|
762
1082
|
`The exact competitor ASINs to build the niche from, ${DIVE_FROM_COMPETITORS_MIN_ASINS} to ${DIVE_FROM_COMPETITORS_MAX_ASINS}, each once. No other competitors are added.`
|
|
763
1083
|
),
|
|
764
|
-
confirm:
|
|
1084
|
+
confirm: z22.boolean().optional().describe(
|
|
765
1085
|
"Must be true to proceed \u2014 this dive spends dive tokens for every ASIN. Confirm the cost with the user first."
|
|
766
1086
|
)
|
|
767
1087
|
};
|
|
@@ -769,7 +1089,7 @@ var createNicheDiveFromCompetitorsListTool = {
|
|
|
769
1089
|
name: "create_niche_dive_from_competitors_list",
|
|
770
1090
|
title: "Create a Niche Dive from a Competitor List",
|
|
771
1091
|
description: `Use this when the user already knows which products belong in the niche \u2014 e.g. they list competitor ASINs, or picked them from search results \u2014 and wants niche research on exactly those. Unlike \`create_niche_dive\`, which discovers competitors around one seed ASIN, this builds the niche from the given ASINs only (${DIVE_FROM_COMPETITORS_MIN_ASINS}-${DIVE_FROM_COMPETITORS_MAX_ASINS}). \u26A0\uFE0F Spends dive tokens for every ASIN and cannot be undone \u2014 set \`confirm: true\` only after the user approves the cost. Not safe to retry: each call spends tokens again and starts a separate dive \u2014 if a call errors or times out, poll \`get_dive_status\` (or check \`list_niches\`) instead of re-calling. Fails without spending tokens when the ASIN count is over the plan's limit, when the account is at its niche limit, or when Amazon product data cannot be found for enough of the ASINs. The dive runs asynchronously: this returns a \`diveId\` and an \`estimatedCompletionDate\`; poll \`get_dive_status\` with the \`diveId\` until it reports \`success\`, which carries the new \`nicheId\`.`,
|
|
772
|
-
inputSchema:
|
|
1092
|
+
inputSchema: inputSchema24,
|
|
773
1093
|
// Additive rather than destructive, but flagged destructive so clients prompt before the
|
|
774
1094
|
// irreversible token spend — same reasoning as create_niche_dive. The confirm gate enforces it.
|
|
775
1095
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
@@ -789,23 +1109,23 @@ var createNicheDiveFromCompetitorsListTool = {
|
|
|
789
1109
|
};
|
|
790
1110
|
|
|
791
1111
|
// src/tools/redive-niche.ts
|
|
792
|
-
import { z as
|
|
793
|
-
var
|
|
794
|
-
nicheId:
|
|
795
|
-
mode:
|
|
1112
|
+
import { z as z23 } from "zod";
|
|
1113
|
+
var inputSchema25 = {
|
|
1114
|
+
nicheId: z23.string().min(1).describe("The niche to re-dive. Get one from `list_niches`."),
|
|
1115
|
+
mode: z23.enum(["same_competitors", "discover"]).describe(
|
|
796
1116
|
"How to pick the competitors for the refreshed dive. `same_competitors` re-runs the niche's current competitor set and takes no other argument \u2014 use it to refresh stale data. `discover` searches for a fresh competitor set, which is what you want when the niche has changed."
|
|
797
1117
|
),
|
|
798
|
-
numberOfCompetitors:
|
|
1118
|
+
numberOfCompetitors: z23.number().int().min(2).optional().describe(
|
|
799
1119
|
"Total ASINs the refreshed dive should contain, including `lockedAsins`. Required in `discover` mode and rejected in `same_competitors` mode. Dive tokens are spent per ASIN."
|
|
800
1120
|
),
|
|
801
|
-
heroAsin:
|
|
1121
|
+
heroAsin: z23.string().min(1).optional().describe(
|
|
802
1122
|
"`discover` mode only. Seed product the competitor discovery starts from. Defaults to the niche's highest-selling competitor, preferring one the user's own connected seller account owns."
|
|
803
1123
|
),
|
|
804
|
-
lockedAsins:
|
|
1124
|
+
lockedAsins: z23.array(z23.string().min(1)).optional().describe(
|
|
805
1125
|
"`discover` mode only. ASINs that must stay in the competitor set regardless of what discovery finds. Cannot exceed `numberOfCompetitors`, and cannot overlap `excludedAsins`."
|
|
806
1126
|
),
|
|
807
|
-
excludedAsins:
|
|
808
|
-
confirm:
|
|
1127
|
+
excludedAsins: z23.array(z23.string().min(1)).optional().describe("`discover` mode only. ASINs discovery must never select."),
|
|
1128
|
+
confirm: z23.boolean().optional().describe(
|
|
809
1129
|
"Must be true to proceed \u2014 a re-dive spends dive tokens. Confirm the cost with the user first."
|
|
810
1130
|
)
|
|
811
1131
|
};
|
|
@@ -813,7 +1133,7 @@ var rediveNicheTool = {
|
|
|
813
1133
|
name: "redive_niche",
|
|
814
1134
|
title: "Re-dive an Existing Niche",
|
|
815
1135
|
description: "Use this to refresh an existing niche's research with current Amazon data, instead of creating a new niche with `create_niche_dive`. \u26A0\uFE0F Spends dive tokens (one batch per ASIN dived) and cannot be undone \u2014 set `confirm: true` only after the user approves the cost. Not safe to retry: each call spends tokens again and starts a separate re-dive \u2014 if a call errors or times out, poll `get_dive_status` instead of re-calling. Two modes: `same_competitors` re-dives the niche's current competitor set (no other argument needed) and `discover` finds a fresh set, sized by `numberOfCompetitors` and steerable with `heroAsin` / `lockedAsins` / `excludedAsins`. Runs asynchronously: returns a `diveId` and an `estimatedCompletionDate` \u2014 poll `get_dive_status` with that `diveId` until it reports `success`. The niche keeps its `nicheId`, so existing rank radars and reports follow the refreshed data.",
|
|
816
|
-
inputSchema:
|
|
1136
|
+
inputSchema: inputSchema25,
|
|
817
1137
|
// Same reasoning as create_niche_dive: a re-dive is additive rather than destructive, but the
|
|
818
1138
|
// irreversible token spend is what clients need to prompt on. The confirm gate enforces it regardless.
|
|
819
1139
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
@@ -846,9 +1166,9 @@ var rediveNicheTool = {
|
|
|
846
1166
|
};
|
|
847
1167
|
|
|
848
1168
|
// src/tools/get-dive-status.ts
|
|
849
|
-
import { z as
|
|
850
|
-
var
|
|
851
|
-
diveId:
|
|
1169
|
+
import { z as z24 } from "zod";
|
|
1170
|
+
var inputSchema26 = {
|
|
1171
|
+
diveId: z24.string().min(1).describe(
|
|
852
1172
|
"The dive identifier returned by `create_niche_dive`, `create_niche_dive_from_competitors_list` or `redive_niche`, or the `refresh.diveId` returned by `add_niche_keywords`."
|
|
853
1173
|
)
|
|
854
1174
|
};
|
|
@@ -856,7 +1176,7 @@ var getDiveStatusTool = {
|
|
|
856
1176
|
name: "get_dive_status",
|
|
857
1177
|
title: "Get Niche Dive Status",
|
|
858
1178
|
description: "Use this to poll a niche dive started with `create_niche_dive`, `create_niche_dive_from_competitors_list` or `redive_niche`, or the re-dive `add_niche_keywords` started on a stale niche, until it finishes. Returns one of three shapes keyed by `status`: `in_progress` (with `estimatedCompletionDate`), `success` (with the `nicheId` plus `tokensUsed`/`tokensLeft`), or `error` (with an `error` message). On `success`, use the `nicheId` with `list_niches`, `get_niche_keywords`, `get_niche_competitors`, etc. (for a re-dive it is the same niche you passed in, now carrying refreshed data). Re-poll periodically \u2014 dives can take minutes; the `estimatedCompletionDate` hints at when to check.",
|
|
859
|
-
inputSchema:
|
|
1179
|
+
inputSchema: inputSchema26,
|
|
860
1180
|
annotations: { readOnlyHint: true },
|
|
861
1181
|
handler: async (args, ctx) => {
|
|
862
1182
|
return await httpGet(
|
|
@@ -867,31 +1187,31 @@ var getDiveStatusTool = {
|
|
|
867
1187
|
};
|
|
868
1188
|
|
|
869
1189
|
// src/tools/add-niche-keywords.ts
|
|
870
|
-
import { z as
|
|
1190
|
+
import { z as z26 } from "zod";
|
|
871
1191
|
|
|
872
1192
|
// src/tools/niche-keyword-list.ts
|
|
873
|
-
import { z as
|
|
874
|
-
var nicheKeyword = () =>
|
|
1193
|
+
import { z as z25 } from "zod";
|
|
1194
|
+
var nicheKeyword = () => z25.string().trim().toLowerCase().min(1).max(NICHE_KEYWORD_MAX_LENGTH);
|
|
875
1195
|
function uniqueKeywords(schema, field) {
|
|
876
1196
|
return schema.refine((keywords) => new Set(keywords).size === keywords.length, {
|
|
877
1197
|
message: `${field} must not contain the same keyword twice (compared lower-cased and trimmed)`
|
|
878
1198
|
});
|
|
879
1199
|
}
|
|
880
1200
|
var nicheKeywordList = uniqueKeywords(
|
|
881
|
-
|
|
1201
|
+
z25.array(nicheKeyword()).min(1).max(NICHE_KEYWORDS_MAX_PER_CALL),
|
|
882
1202
|
"keywords"
|
|
883
1203
|
);
|
|
884
1204
|
|
|
885
1205
|
// src/tools/add-niche-keywords.ts
|
|
886
|
-
var
|
|
887
|
-
nicheId:
|
|
1206
|
+
var inputSchema27 = {
|
|
1207
|
+
nicheId: z26.string().min(1).describe("The niche whose Master Keyword List to add to. Get one from `list_niches`."),
|
|
888
1208
|
keywords: nicheKeywordList.describe(
|
|
889
1209
|
`The keywords to add, 1\u2013${NICHE_KEYWORDS_MAX_PER_CALL} per call, at most 255 characters each. They are lower-cased and trimmed, and must be unique after that. Put every keyword in one call: this tool allows only 2 calls per minute per niche.`
|
|
890
1210
|
),
|
|
891
|
-
confirm:
|
|
1211
|
+
confirm: z26.boolean().optional().describe(
|
|
892
1212
|
"Leave unset on the first call. Set to true only after the user approved the Dive token cost from a `confirmation_required` response \u2014 it lets the add re-dive a stale niche."
|
|
893
1213
|
),
|
|
894
|
-
maxTokensUsed:
|
|
1214
|
+
maxTokensUsed: z26.number().int().min(0).optional().describe(
|
|
895
1215
|
"With `confirm: true`: the `tokenCost` the user approved. The call then fails with nothing saved or charged rather than spend more. Ignored without `confirm`, where the tool never spends anything."
|
|
896
1216
|
)
|
|
897
1217
|
};
|
|
@@ -904,7 +1224,7 @@ var addNicheKeywordsTool = {
|
|
|
904
1224
|
name: "add_niche_keywords",
|
|
905
1225
|
title: "Add Keywords to a Niche's Master Keyword List",
|
|
906
1226
|
description: "Use this to add keywords to a niche's Master Keyword List (MKL): the user's own search terms, or keywords from the niche's Outliers or Residue lists. Free while the niche is fresh (dived in the last 24 hours, or a dive is running): the first call adds the keywords at once and they are readable with `get_niche_keywords`. On a stale niche, any keyword new to the niche makes the add re-dive the whole niche with its current competitors, which spends one Dive token per competitor. The first call then saves and spends nothing and returns `confirmation_required` with `tokenCost`; after the user approves, call again with `confirm: true` and `maxTokensUsed` set to that `tokenCost`. Keywords moved in from Outliers or Residue never cost anything. Returns `originBreakdown` (`alreadyInMkl`, `takenFromOutliers`, `takenFromResidue`, `addedManually`) and `tokensUsed`. When `refresh` is present a re-dive is running: poll `get_dive_status` with `refresh.diveId`; the `addedManually` keywords show in the MKL once it succeeds. A confirmed call is not safe to retry: if it errors or times out, poll `get_dive_status` or re-read `get_niche_keywords` instead of re-calling. Limited to 2 calls per minute per niche, so batch keywords into one call. Undo with `remove_niche_keywords`. A keyword hidden by the niche's keyword exclusions stays hidden after an add: check `list_keyword_exclusions` and remove the exclusion with `remove_keyword_exclusions` first.",
|
|
907
|
-
inputSchema:
|
|
1227
|
+
inputSchema: inputSchema27,
|
|
908
1228
|
// Like redive_niche: additive, but it can spend Dive tokens irreversibly, which is what clients
|
|
909
1229
|
// need to prompt on. The confirm gate covers the paid path regardless.
|
|
910
1230
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
@@ -938,9 +1258,9 @@ var addNicheKeywordsTool = {
|
|
|
938
1258
|
};
|
|
939
1259
|
|
|
940
1260
|
// src/tools/remove-niche-keywords.ts
|
|
941
|
-
import { z as
|
|
942
|
-
var
|
|
943
|
-
nicheId:
|
|
1261
|
+
import { z as z27 } from "zod";
|
|
1262
|
+
var inputSchema28 = {
|
|
1263
|
+
nicheId: z27.string().min(1).describe("The niche whose Master Keyword List to remove from. Get one from `list_niches`."),
|
|
944
1264
|
keywords: nicheKeywordList.describe(
|
|
945
1265
|
`The keywords to take out, 1\u2013${NICHE_KEYWORDS_MAX_PER_CALL} per call, at most 255 characters each. They are lower-cased and trimmed, and must be unique after that. Put every keyword in one call: this tool allows only 2 calls per minute per niche.`
|
|
946
1266
|
)
|
|
@@ -949,7 +1269,7 @@ var removeNicheKeywordsTool = {
|
|
|
949
1269
|
name: "remove_niche_keywords",
|
|
950
1270
|
title: "Remove Keywords from a Niche's Master Keyword List",
|
|
951
1271
|
description: "Use this to undo `add_niche_keywords`: take keywords back out of a niche's Master Keyword List (MKL). Only keywords that were added to the MKL can be removed \u2014 `get_niche_keywords` shows each keyword's `origin`. What happens depends on that origin, and the response reports it per keyword: `removedManual` \u2014 added by hand (`manual`), deleted from the niche; `sentBackToOutliers` / `sentBackToResidue` \u2014 moved in from those lists (`outlier` / `residue`), listed there again with their data kept; `notRemovable` \u2014 found by the dive itself (`mkl`) or not in the niche, left unchanged. To hide a keyword the dive found, use `add_keyword_exclusions` instead. Free, and safe to repeat: a keyword already removed comes back under `notRemovable`. Adding a hand-added keyword back later re-dives the niche and costs Dive tokens if the niche is stale by then. Limited to 2 calls per minute per niche.",
|
|
952
|
-
inputSchema:
|
|
1272
|
+
inputSchema: inputSchema28,
|
|
953
1273
|
// Deletes hand-added keywords, and putting them back can cost Dive tokens on a stale niche.
|
|
954
1274
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
955
1275
|
handler: async (args, ctx) => {
|
|
@@ -962,10 +1282,10 @@ var removeNicheKeywordsTool = {
|
|
|
962
1282
|
};
|
|
963
1283
|
|
|
964
1284
|
// src/tools/keyword-exclusions-input.ts
|
|
965
|
-
import { z as
|
|
966
|
-
var entries = (field) => uniqueKeywords(
|
|
1285
|
+
import { z as z28 } from "zod";
|
|
1286
|
+
var entries = (field) => uniqueKeywords(z28.array(nicheKeyword()).max(KEYWORD_EXCLUSIONS_MAX_PER_CALL), field).optional();
|
|
967
1287
|
var keywordExclusionsInputSchema = (verb) => ({
|
|
968
|
-
nicheId:
|
|
1288
|
+
nicheId: z28.string().min(1).describe("The niche whose keyword exclusions to change. Get one from `list_niches`."),
|
|
969
1289
|
exact: entries("exact").describe(
|
|
970
1290
|
`Exact-match entries to ${verb}: each one hides the single keyword that matches it exactly. At most 255 characters each; lower-cased and trimmed, and must be unique after that.`
|
|
971
1291
|
),
|
|
@@ -985,12 +1305,12 @@ function assertExclusionCount(exact, phrase) {
|
|
|
985
1305
|
}
|
|
986
1306
|
|
|
987
1307
|
// src/tools/add-keyword-exclusions.ts
|
|
988
|
-
var
|
|
1308
|
+
var inputSchema29 = keywordExclusionsInputSchema("exclude");
|
|
989
1309
|
var addKeywordExclusionsTool = {
|
|
990
1310
|
name: "add_keyword_exclusions",
|
|
991
1311
|
title: "Exclude Keywords from a Niche",
|
|
992
1312
|
description: `Use this to hide irrelevant keywords from a niche's Master Keyword List \u2014 e.g. other brands, wrong product types. \`exact\` entries hide the one keyword that matches exactly; \`phrase\` entries hide every keyword containing the phrase, so prefer \`exact\` unless the user wants a whole family of keywords gone. The excluded keywords drop out of \`get_niche_keywords\` at once, wherever they come from: the dive, Outliers, Residue, or ones added with \`add_niche_keywords\`. Nothing is deleted: \`remove_keyword_exclusions\` brings them back. Only the exclusions change \u2014 every other niche setting is kept. Send 1\u2013${KEYWORD_EXCLUSIONS_MAX_PER_CALL} entries in total across \`exact\` and \`phrase\`; a niche can hold at most ${KEYWORD_EXCLUSIONS_MAX_PER_NICHE} exclusions. Free, no tokens, and safe to repeat: entries already excluded come back under \`unchanged\`. Returns \`added\`, \`unchanged\` and the full \`exact\` / \`phrase\` lists after the change. Limited to 30 calls per minute per niche, so batch entries into one call.`,
|
|
993
|
-
inputSchema:
|
|
1313
|
+
inputSchema: inputSchema29,
|
|
994
1314
|
// Hides data from every MKL-based view until undone; reversible, but clients should prompt on it.
|
|
995
1315
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
996
1316
|
handler: async (args, ctx) => {
|
|
@@ -1004,12 +1324,12 @@ var addKeywordExclusionsTool = {
|
|
|
1004
1324
|
};
|
|
1005
1325
|
|
|
1006
1326
|
// src/tools/remove-keyword-exclusions.ts
|
|
1007
|
-
var
|
|
1327
|
+
var inputSchema30 = keywordExclusionsInputSchema("stop excluding");
|
|
1008
1328
|
var removeKeywordExclusionsTool = {
|
|
1009
1329
|
name: "remove_keyword_exclusions",
|
|
1010
1330
|
title: "Remove Keyword Exclusions from a Niche",
|
|
1011
1331
|
description: `Use this to undo \`add_keyword_exclusions\`, so the keywords an exclusion hid show in the niche's Master Keyword List (\`get_niche_keywords\`) again. Pass the entries exactly as \`list_keyword_exclusions\` shows them, \`exact\` and \`phrase\` separately: removing a phrase does not touch an exact entry for the same text, and a keyword also hidden by another remaining exclusion stays hidden. Only the exclusions change \u2014 every other niche setting is kept. Send 1\u2013${KEYWORD_EXCLUSIONS_MAX_PER_CALL} entries in total across \`exact\` and \`phrase\`. Free, and safe to repeat: entries that are not excluded come back under \`unchanged\`. Returns \`removed\`, \`unchanged\` and the full \`exact\` / \`phrase\` lists after the change. Limited to 30 calls per minute per niche.`,
|
|
1012
|
-
inputSchema:
|
|
1332
|
+
inputSchema: inputSchema30,
|
|
1013
1333
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
1014
1334
|
handler: async (args, ctx) => {
|
|
1015
1335
|
assertExclusionCount(args.exact, args.phrase);
|
|
@@ -1022,10 +1342,10 @@ var removeKeywordExclusionsTool = {
|
|
|
1022
1342
|
};
|
|
1023
1343
|
|
|
1024
1344
|
// src/tools/delete-niche.ts
|
|
1025
|
-
import { z as
|
|
1026
|
-
var
|
|
1027
|
-
nicheId:
|
|
1028
|
-
confirm:
|
|
1345
|
+
import { z as z29 } from "zod";
|
|
1346
|
+
var inputSchema31 = {
|
|
1347
|
+
nicheId: z29.string().min(1).describe("The niche to delete. Get one from `list_niches`."),
|
|
1348
|
+
confirm: z29.boolean().optional().describe(
|
|
1029
1349
|
"Must be true to proceed \u2014 deleting a niche destroys its keywords, competitors and dive history permanently. Confirm with the user first."
|
|
1030
1350
|
)
|
|
1031
1351
|
};
|
|
@@ -1033,7 +1353,7 @@ var deleteNicheTool = {
|
|
|
1033
1353
|
name: "delete_niche",
|
|
1034
1354
|
title: "Delete a Niche",
|
|
1035
1355
|
description: "Use this to permanently remove a niche the user no longer needs. \u26A0\uFE0F Deletes the niche and everything attached to it \u2014 keywords, competitors, all dives and diveboxes \u2014 and cannot be undone; the dive tokens already spent on it are NOT refunded. Set `confirm: true` only after the user approves. A niche used by any Rank Radar cannot be deleted: the call fails with a conflict, so delete or archive those rank radars first (`list_rank_radars` with the `nicheId` filter shows them).",
|
|
1036
|
-
inputSchema:
|
|
1356
|
+
inputSchema: inputSchema31,
|
|
1037
1357
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
1038
1358
|
handler: async (args, ctx) => {
|
|
1039
1359
|
const pending = requireConfirmation(
|
|
@@ -1051,15 +1371,15 @@ var deleteNicheTool = {
|
|
|
1051
1371
|
};
|
|
1052
1372
|
|
|
1053
1373
|
// src/tools/pause-rank-radar.ts
|
|
1054
|
-
import { z as
|
|
1055
|
-
var
|
|
1056
|
-
rankRadarId:
|
|
1374
|
+
import { z as z30 } from "zod";
|
|
1375
|
+
var inputSchema32 = {
|
|
1376
|
+
rankRadarId: z30.string().uuid().describe("The Rank Radar UUID to pause (from `list_rank_radars`).")
|
|
1057
1377
|
};
|
|
1058
1378
|
var pauseRankRadarTool = {
|
|
1059
1379
|
name: "pause_rank_radar",
|
|
1060
1380
|
title: "Pause a Rank Radar",
|
|
1061
1381
|
description: "Use this to pause tracking on a Rank Radar without losing anything \u2014 the action the DataDive API calls 'archiving', so when a user asks to archive a Rank Radar, this is the tool they mean. Tracking stops for all of its keywords and the Daily Tracked Keywords quota they held is freed for other Rank Radars, while the Rank Radar and its ranking history are kept. Fully reversible with `resume_rank_radar`, so it needs no `confirm` \u2014 prefer it over `delete_rank_radar` whenever the user may want the data back. Paused Rank Radars show up in `list_rank_radars` under `status: PAUSED` \u2014 not `ARCHIVED`, which covers Rank Radars removed with `delete_rank_radar`. Safe to repeat: pausing an already paused Rank Radar succeeds and changes nothing.",
|
|
1062
|
-
inputSchema:
|
|
1382
|
+
inputSchema: inputSchema32,
|
|
1063
1383
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
1064
1384
|
handler: async (args, ctx) => {
|
|
1065
1385
|
await httpPost(
|
|
@@ -1072,15 +1392,15 @@ var pauseRankRadarTool = {
|
|
|
1072
1392
|
};
|
|
1073
1393
|
|
|
1074
1394
|
// src/tools/resume-rank-radar.ts
|
|
1075
|
-
import { z as
|
|
1076
|
-
var
|
|
1077
|
-
rankRadarId:
|
|
1395
|
+
import { z as z31 } from "zod";
|
|
1396
|
+
var inputSchema33 = {
|
|
1397
|
+
rankRadarId: z31.string().uuid().describe("The Rank Radar UUID to resume (from `list_rank_radars`).")
|
|
1078
1398
|
};
|
|
1079
1399
|
var resumeRankRadarTool = {
|
|
1080
1400
|
name: "resume_rank_radar",
|
|
1081
1401
|
title: "Resume a Paused Rank Radar",
|
|
1082
1402
|
description: "Use this to restart tracking on a Rank Radar that was paused with `pause_rank_radar` (shown by `list_rank_radars` under `status: PAUSED`; a deleted, `ARCHIVED` one cannot be resumed). Keywords are resumed as far as the available Daily Tracked Keywords quota allows, most relevant first \u2014 so with a tight quota only part of the original keyword set comes back; check `get_quota` first if that matters. Reversible with `pause_rank_radar`, so it needs no `confirm`. Fails with a bad-request error when there is no quota left at all. Safe to repeat: resuming an already active Rank Radar changes nothing.",
|
|
1083
|
-
inputSchema:
|
|
1403
|
+
inputSchema: inputSchema33,
|
|
1084
1404
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
1085
1405
|
handler: async (args, ctx) => {
|
|
1086
1406
|
await httpPost(
|
|
@@ -1093,10 +1413,10 @@ var resumeRankRadarTool = {
|
|
|
1093
1413
|
};
|
|
1094
1414
|
|
|
1095
1415
|
// src/tools/delete-rank-radar.ts
|
|
1096
|
-
import { z as
|
|
1097
|
-
var
|
|
1098
|
-
rankRadarId:
|
|
1099
|
-
confirm:
|
|
1416
|
+
import { z as z32 } from "zod";
|
|
1417
|
+
var inputSchema34 = {
|
|
1418
|
+
rankRadarId: z32.string().uuid().describe("The Rank Radar UUID to delete (from `list_rank_radars`)."),
|
|
1419
|
+
confirm: z32.boolean().optional().describe(
|
|
1100
1420
|
"Must be true to proceed \u2014 deleting a Rank Radar destroys its ranking history permanently. Confirm with the user first."
|
|
1101
1421
|
)
|
|
1102
1422
|
};
|
|
@@ -1104,7 +1424,7 @@ var deleteRankRadarTool = {
|
|
|
1104
1424
|
name: "delete_rank_radar",
|
|
1105
1425
|
title: "Delete a Rank Radar",
|
|
1106
1426
|
description: "Use this to permanently remove a Rank Radar and all of its keyword ranking history. \u26A0\uFE0F Cannot be undone \u2014 there is no restore endpoint; set `confirm: true` only after the user approves. It does free the Daily Tracked Keywords quota those keywords held. Deleted Rank Radars are what `list_rank_radars` returns for `status: ARCHIVED`. If the user only wants to stop tracking for a while and keep the history \u2014 or asks to 'archive' the Rank Radar, which is what the DataDive API calls pausing \u2014 use `pause_rank_radar` instead: it frees the same quota and is reversible with `resume_rank_radar`.",
|
|
1107
|
-
inputSchema:
|
|
1427
|
+
inputSchema: inputSchema34,
|
|
1108
1428
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
1109
1429
|
handler: async (args, ctx) => {
|
|
1110
1430
|
const pending = requireConfirmation(
|
|
@@ -1122,10 +1442,10 @@ var deleteRankRadarTool = {
|
|
|
1122
1442
|
};
|
|
1123
1443
|
|
|
1124
1444
|
// src/tools/add-rank-radar-search-terms.ts
|
|
1125
|
-
import { z as
|
|
1126
|
-
var
|
|
1127
|
-
rankRadarId:
|
|
1128
|
-
searchTerms:
|
|
1445
|
+
import { z as z33 } from "zod";
|
|
1446
|
+
var inputSchema35 = {
|
|
1447
|
+
rankRadarId: z33.string().uuid().describe("The Rank Radar UUID to add the search terms to (from `list_rank_radars`)."),
|
|
1448
|
+
searchTerms: z33.array(z33.string().min(1)).min(1).max(ADD_SEARCH_TERMS_MAX).describe(
|
|
1129
1449
|
`The keywords to start tracking, 1\u2013${ADD_SEARCH_TERMS_MAX} per call. Must be unique. Amazon-ignored special characters (@, #, _, parentheses, \u2026) are rejected.`
|
|
1130
1450
|
)
|
|
1131
1451
|
};
|
|
@@ -1133,7 +1453,7 @@ var addRankRadarSearchTermsTool = {
|
|
|
1133
1453
|
name: "add_rank_radar_search_terms",
|
|
1134
1454
|
title: "Add Search Terms to a Rank Radar",
|
|
1135
1455
|
description: "Use this to start tracking extra keywords on an existing Rank Radar, instead of creating a new one with `create_rank_radar`. New keywords are added to the niche and tracked from now on; any matching keyword that was previously paused is resumed. Each newly tracked keyword takes one Daily Tracked Keywords slot, and the call fails if the quota is exhausted \u2014 check `get_quota` first. Reversible with `pause_rank_radar_search_terms`, which frees the slots again, so it needs no `confirm`. Returns `originBreakdown` (where each submitted term ended up) and `keywordToRankRadarKeywordIdMap`, whose ids are what the pause/resume search-term tools take.",
|
|
1136
|
-
inputSchema:
|
|
1456
|
+
inputSchema: inputSchema35,
|
|
1137
1457
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
1138
1458
|
handler: async (args, ctx) => {
|
|
1139
1459
|
return await httpPost(
|
|
@@ -1145,10 +1465,10 @@ var addRankRadarSearchTermsTool = {
|
|
|
1145
1465
|
};
|
|
1146
1466
|
|
|
1147
1467
|
// src/tools/pause-rank-radar-search-terms.ts
|
|
1148
|
-
import { z as
|
|
1149
|
-
var
|
|
1150
|
-
rankRadarId:
|
|
1151
|
-
rankRadarKeywordIds:
|
|
1468
|
+
import { z as z34 } from "zod";
|
|
1469
|
+
var inputSchema36 = {
|
|
1470
|
+
rankRadarId: z34.string().uuid().describe("The Rank Radar UUID the search terms belong to."),
|
|
1471
|
+
rankRadarKeywordIds: z34.array(z34.string().uuid()).min(1).describe(
|
|
1152
1472
|
"The Rank Radar keyword ids to pause. These are the `id` values from `get_rank_radar_data`, or the values of `keywordToRankRadarKeywordIdMap` returned by `add_rank_radar_search_terms` \u2014 not the keyword text."
|
|
1153
1473
|
)
|
|
1154
1474
|
};
|
|
@@ -1156,7 +1476,7 @@ var pauseRankRadarSearchTermsTool = {
|
|
|
1156
1476
|
name: "pause_rank_radar_search_terms",
|
|
1157
1477
|
title: "Pause Search Terms of a Rank Radar",
|
|
1158
1478
|
description: "Use this to stop tracking individual keywords on a Rank Radar while keeping the Rank Radar itself active \u2014 the way to trim a keyword set the user finds too broad. The DataDive API calls this 'archiving' the search terms; nothing is lost. The paused keywords keep their history and their Daily Tracked Keywords slots are freed for other keywords. Reversible with `resume_rank_radar_search_terms`, so it needs no `confirm`. Takes keyword ids, not keyword text: get them from `get_rank_radar_data`. Safe to repeat: already-paused terms are left unchanged.",
|
|
1159
|
-
inputSchema:
|
|
1479
|
+
inputSchema: inputSchema36,
|
|
1160
1480
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
1161
1481
|
handler: async (args, ctx) => {
|
|
1162
1482
|
await httpPost(
|
|
@@ -1173,10 +1493,10 @@ var pauseRankRadarSearchTermsTool = {
|
|
|
1173
1493
|
};
|
|
1174
1494
|
|
|
1175
1495
|
// src/tools/resume-rank-radar-search-terms.ts
|
|
1176
|
-
import { z as
|
|
1177
|
-
var
|
|
1178
|
-
rankRadarId:
|
|
1179
|
-
rankRadarKeywordIds:
|
|
1496
|
+
import { z as z35 } from "zod";
|
|
1497
|
+
var inputSchema37 = {
|
|
1498
|
+
rankRadarId: z35.string().uuid().describe("The Rank Radar UUID the search terms belong to."),
|
|
1499
|
+
rankRadarKeywordIds: z35.array(z35.string().uuid()).min(1).describe(
|
|
1180
1500
|
"The Rank Radar keyword ids to resume. These are the `id` values from `get_rank_radar_data`, or the values of `keywordToRankRadarKeywordIdMap` returned by `add_rank_radar_search_terms` \u2014 not the keyword text."
|
|
1181
1501
|
)
|
|
1182
1502
|
};
|
|
@@ -1184,7 +1504,7 @@ var resumeRankRadarSearchTermsTool = {
|
|
|
1184
1504
|
name: "resume_rank_radar_search_terms",
|
|
1185
1505
|
title: "Resume Paused Search Terms of a Rank Radar",
|
|
1186
1506
|
description: "Use this to start tracking keywords again that were paused with `pause_rank_radar_search_terms`. Each resumed keyword takes back one Daily Tracked Keywords slot, and the call fails if the quota is exhausted \u2014 check `get_quota` first. Reversible with `pause_rank_radar_search_terms`, so it needs no `confirm`. Takes keyword ids, not keyword text: get them from `get_rank_radar_data`. Safe to repeat: already-active terms are left unchanged.",
|
|
1187
|
-
inputSchema:
|
|
1507
|
+
inputSchema: inputSchema37,
|
|
1188
1508
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
1189
1509
|
handler: async (args, ctx) => {
|
|
1190
1510
|
await httpPost(
|
|
@@ -1201,18 +1521,18 @@ var resumeRankRadarSearchTermsTool = {
|
|
|
1201
1521
|
};
|
|
1202
1522
|
|
|
1203
1523
|
// src/tools/list-rank-radar-tags.ts
|
|
1204
|
-
import { z as
|
|
1524
|
+
import { z as z36 } from "zod";
|
|
1205
1525
|
var LIST_RANK_RADAR_TAGS_MAX_PAGE_SIZE = 50;
|
|
1206
|
-
var
|
|
1207
|
-
rankRadarId:
|
|
1208
|
-
currentPage:
|
|
1209
|
-
pageSize:
|
|
1526
|
+
var inputSchema38 = {
|
|
1527
|
+
rankRadarId: z36.string().uuid().describe("The Rank Radar UUID whose tags to list (from `list_rank_radars`)."),
|
|
1528
|
+
currentPage: z36.number().int().min(1).optional().describe("Page number, 1-indexed. Defaults to 1."),
|
|
1529
|
+
pageSize: z36.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.`)
|
|
1210
1530
|
};
|
|
1211
1531
|
var listRankRadarTagsTool = {
|
|
1212
1532
|
name: "list_rank_radar_tags",
|
|
1213
1533
|
title: "List Tags of a Rank Radar",
|
|
1214
1534
|
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.`,
|
|
1215
|
-
inputSchema:
|
|
1535
|
+
inputSchema: inputSchema38,
|
|
1216
1536
|
annotations: { readOnlyHint: true },
|
|
1217
1537
|
handler: async (args, ctx) => {
|
|
1218
1538
|
return await httpGet(
|
|
@@ -1227,19 +1547,19 @@ var listRankRadarTagsTool = {
|
|
|
1227
1547
|
};
|
|
1228
1548
|
|
|
1229
1549
|
// src/tools/create-rank-radar-tag.ts
|
|
1230
|
-
import { z as
|
|
1231
|
-
var
|
|
1232
|
-
rankRadarId:
|
|
1233
|
-
title:
|
|
1550
|
+
import { z as z37 } from "zod";
|
|
1551
|
+
var inputSchema39 = {
|
|
1552
|
+
rankRadarId: z37.string().uuid().describe("The Rank Radar UUID to create the tag on (from `list_rank_radars`)."),
|
|
1553
|
+
title: z37.string().min(1).max(TAG_TITLE_MAX_LENGTH).describe(
|
|
1234
1554
|
`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.`
|
|
1235
1555
|
),
|
|
1236
|
-
color:
|
|
1556
|
+
color: z37.string().regex(TAG_HEX_COLOR_PATTERN, "color must be in #RRGGBB form").optional().describe(
|
|
1237
1557
|
`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.`
|
|
1238
1558
|
),
|
|
1239
|
-
description:
|
|
1240
|
-
startAt:
|
|
1241
|
-
endAt:
|
|
1242
|
-
rankRadarKeywordIds:
|
|
1559
|
+
description: z37.string().optional().describe("Free-form description, e.g. 'Prime Day run-up'."),
|
|
1560
|
+
startAt: z37.string().optional().describe("ISO 8601 start date, e.g. '2026-07-01'. Required if `endAt` is set; `endAt >= startAt`."),
|
|
1561
|
+
endAt: z37.string().optional().describe("ISO 8601 end date, e.g. '2026-07-14'. Must be greater than or equal to `startAt`."),
|
|
1562
|
+
rankRadarKeywordIds: z37.array(z37.string().uuid()).min(1).max(TAG_KEYWORD_IDS_MAX).optional().describe(
|
|
1243
1563
|
`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\`.`
|
|
1244
1564
|
)
|
|
1245
1565
|
};
|
|
@@ -1247,7 +1567,7 @@ var createRankRadarTagTool = {
|
|
|
1247
1567
|
name: "create_rank_radar_tag",
|
|
1248
1568
|
title: "Create a Rank Radar Tag",
|
|
1249
1569
|
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\`.`,
|
|
1250
|
-
inputSchema:
|
|
1570
|
+
inputSchema: inputSchema39,
|
|
1251
1571
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
1252
1572
|
handler: async (args, ctx) => {
|
|
1253
1573
|
const body = { title: args.title };
|
|
@@ -1265,23 +1585,23 @@ var createRankRadarTagTool = {
|
|
|
1265
1585
|
};
|
|
1266
1586
|
|
|
1267
1587
|
// src/tools/update-rank-radar-tag.ts
|
|
1268
|
-
import { z as
|
|
1269
|
-
var
|
|
1270
|
-
rankRadarId:
|
|
1271
|
-
tagId:
|
|
1272
|
-
title:
|
|
1588
|
+
import { z as z38 } from "zod";
|
|
1589
|
+
var inputSchema40 = {
|
|
1590
|
+
rankRadarId: z38.string().uuid().describe("The Rank Radar UUID the tag belongs to (from `list_rank_radars`)."),
|
|
1591
|
+
tagId: z38.string().uuid().describe("The tag UUID to update (the `id` from `list_rank_radar_tags` or `create_rank_radar_tag`)."),
|
|
1592
|
+
title: z38.string().min(1).max(TAG_TITLE_MAX_LENGTH).optional().describe(
|
|
1273
1593
|
`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 }\`.`
|
|
1274
1594
|
),
|
|
1275
|
-
color:
|
|
1276
|
-
description:
|
|
1277
|
-
startAt:
|
|
1278
|
-
endAt:
|
|
1595
|
+
color: z38.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.`),
|
|
1596
|
+
description: z38.string().optional().describe("Free-form description. Pass an empty string to clear."),
|
|
1597
|
+
startAt: z38.string().optional().describe("ISO 8601 start date, e.g. '2026-07-01'. `endAt >= startAt`."),
|
|
1598
|
+
endAt: z38.string().optional().describe("ISO 8601 end date, e.g. '2026-07-14'. `endAt >= startAt`.")
|
|
1279
1599
|
};
|
|
1280
1600
|
var updateRankRadarTagTool = {
|
|
1281
1601
|
name: "update_rank_radar_tag",
|
|
1282
1602
|
title: "Update a Rank Radar Tag",
|
|
1283
1603
|
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).",
|
|
1284
|
-
inputSchema:
|
|
1604
|
+
inputSchema: inputSchema40,
|
|
1285
1605
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
1286
1606
|
handler: async (args, ctx) => {
|
|
1287
1607
|
const body = {};
|
|
@@ -1299,11 +1619,11 @@ var updateRankRadarTagTool = {
|
|
|
1299
1619
|
};
|
|
1300
1620
|
|
|
1301
1621
|
// src/tools/delete-rank-radar-tag.ts
|
|
1302
|
-
import { z as
|
|
1303
|
-
var
|
|
1304
|
-
rankRadarId:
|
|
1305
|
-
tagId:
|
|
1306
|
-
confirm:
|
|
1622
|
+
import { z as z39 } from "zod";
|
|
1623
|
+
var inputSchema41 = {
|
|
1624
|
+
rankRadarId: z39.string().uuid().describe("The Rank Radar UUID the tag belongs to (from `list_rank_radars`)."),
|
|
1625
|
+
tagId: z39.string().uuid().describe("The tag UUID to delete (the `id` from `list_rank_radar_tags`)."),
|
|
1626
|
+
confirm: z39.boolean().optional().describe(
|
|
1307
1627
|
"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."
|
|
1308
1628
|
)
|
|
1309
1629
|
};
|
|
@@ -1311,7 +1631,7 @@ var deleteRankRadarTagTool = {
|
|
|
1311
1631
|
name: "delete_rank_radar_tag",
|
|
1312
1632
|
title: "Delete a Rank Radar Tag",
|
|
1313
1633
|
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.',
|
|
1314
|
-
inputSchema:
|
|
1634
|
+
inputSchema: inputSchema41,
|
|
1315
1635
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
1316
1636
|
handler: async (args, ctx) => {
|
|
1317
1637
|
const pending = requireConfirmation(
|
|
@@ -1329,11 +1649,11 @@ var deleteRankRadarTagTool = {
|
|
|
1329
1649
|
};
|
|
1330
1650
|
|
|
1331
1651
|
// src/tools/add-rank-radar-tag-search-terms.ts
|
|
1332
|
-
import { z as
|
|
1333
|
-
var
|
|
1334
|
-
rankRadarId:
|
|
1335
|
-
tagId:
|
|
1336
|
-
rankRadarKeywordIds:
|
|
1652
|
+
import { z as z40 } from "zod";
|
|
1653
|
+
var inputSchema42 = {
|
|
1654
|
+
rankRadarId: z40.string().uuid().describe("The Rank Radar UUID the tag belongs to (from `list_rank_radars`)."),
|
|
1655
|
+
tagId: z40.string().uuid().describe("The tag UUID to attach keywords to (the `id` from `list_rank_radar_tags`)."),
|
|
1656
|
+
rankRadarKeywordIds: z40.array(z40.string().uuid()).min(1).max(TAG_KEYWORD_IDS_MAX).describe(
|
|
1337
1657
|
`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.`
|
|
1338
1658
|
)
|
|
1339
1659
|
};
|
|
@@ -1341,7 +1661,7 @@ var addRankRadarTagSearchTermsTool = {
|
|
|
1341
1661
|
name: "add_rank_radar_tag_search_terms",
|
|
1342
1662
|
title: "Attach Search Terms to a Rank Radar Tag",
|
|
1343
1663
|
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.`,
|
|
1344
|
-
inputSchema:
|
|
1664
|
+
inputSchema: inputSchema42,
|
|
1345
1665
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
1346
1666
|
handler: async (args, ctx) => {
|
|
1347
1667
|
return await httpPost(
|
|
@@ -1353,11 +1673,11 @@ var addRankRadarTagSearchTermsTool = {
|
|
|
1353
1673
|
};
|
|
1354
1674
|
|
|
1355
1675
|
// src/tools/remove-rank-radar-tag-search-terms.ts
|
|
1356
|
-
import { z as
|
|
1357
|
-
var
|
|
1358
|
-
rankRadarId:
|
|
1359
|
-
tagId:
|
|
1360
|
-
rankRadarKeywordIds:
|
|
1676
|
+
import { z as z41 } from "zod";
|
|
1677
|
+
var inputSchema43 = {
|
|
1678
|
+
rankRadarId: z41.string().uuid().describe("The Rank Radar UUID the tag belongs to (from `list_rank_radars`)."),
|
|
1679
|
+
tagId: z41.string().uuid().describe("The tag UUID to detach keywords from (the `id` from `list_rank_radar_tags`)."),
|
|
1680
|
+
rankRadarKeywordIds: z41.array(z41.string().uuid()).min(1).max(TAG_KEYWORD_IDS_MAX).describe(
|
|
1361
1681
|
`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.`
|
|
1362
1682
|
)
|
|
1363
1683
|
};
|
|
@@ -1365,7 +1685,7 @@ var removeRankRadarTagSearchTermsTool = {
|
|
|
1365
1685
|
name: "remove_rank_radar_tag_search_terms",
|
|
1366
1686
|
title: "Detach Search Terms from a Rank Radar Tag",
|
|
1367
1687
|
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.`,
|
|
1368
|
-
inputSchema:
|
|
1688
|
+
inputSchema: inputSchema43,
|
|
1369
1689
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
1370
1690
|
handler: async (args, ctx) => {
|
|
1371
1691
|
return await httpPost(
|
|
@@ -1377,16 +1697,16 @@ var removeRankRadarTagSearchTermsTool = {
|
|
|
1377
1697
|
};
|
|
1378
1698
|
|
|
1379
1699
|
// src/tools/generate-listing-copy.ts
|
|
1380
|
-
import { z as
|
|
1381
|
-
var
|
|
1382
|
-
nicheId:
|
|
1383
|
-
strategy:
|
|
1700
|
+
import { z as z42 } from "zod";
|
|
1701
|
+
var inputSchema44 = {
|
|
1702
|
+
nicheId: z42.string().min(1).describe("The niche whose keyword data the copy is written from. Get one from `list_niches`."),
|
|
1703
|
+
strategy: z42.enum(LISTING_COPY_STRATEGIES).describe(
|
|
1384
1704
|
"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."
|
|
1385
1705
|
),
|
|
1386
|
-
currentListing:
|
|
1706
|
+
currentListing: z42.record(z42.union([z42.string(), z42.array(z42.string())])).describe(
|
|
1387
1707
|
'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.'
|
|
1388
1708
|
),
|
|
1389
|
-
confirm:
|
|
1709
|
+
confirm: z42.boolean().optional().describe(
|
|
1390
1710
|
"Must be true to proceed \u2014 each generation spends one AI Copywriter prompt from the quota. Confirm the cost with the user first."
|
|
1391
1711
|
)
|
|
1392
1712
|
};
|
|
@@ -1394,7 +1714,7 @@ var generateListingCopyTool = {
|
|
|
1394
1714
|
name: "generate_listing_copy",
|
|
1395
1715
|
title: "Generate Listing Copy for a Niche",
|
|
1396
1716
|
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`.",
|
|
1397
|
-
inputSchema:
|
|
1717
|
+
inputSchema: inputSchema44,
|
|
1398
1718
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
1399
1719
|
handler: async (args, ctx) => {
|
|
1400
1720
|
const pending = requireConfirmation(
|
|
@@ -1412,16 +1732,16 @@ var generateListingCopyTool = {
|
|
|
1412
1732
|
};
|
|
1413
1733
|
|
|
1414
1734
|
// src/tools/get-listing-copy-generation-status.ts
|
|
1415
|
-
import { z as
|
|
1416
|
-
var
|
|
1417
|
-
nicheId:
|
|
1418
|
-
generationId:
|
|
1735
|
+
import { z as z43 } from "zod";
|
|
1736
|
+
var inputSchema45 = {
|
|
1737
|
+
nicheId: z43.string().min(1).describe("The niche the generation was started for."),
|
|
1738
|
+
generationId: z43.string().min(1).describe("The `generationId` returned by `generate_listing_copy`.")
|
|
1419
1739
|
};
|
|
1420
1740
|
var getListingCopyGenerationStatusTool = {
|
|
1421
1741
|
name: "get_listing_copy_generation_status",
|
|
1422
1742
|
title: "Get Listing Copy Generation Status",
|
|
1423
1743
|
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`.",
|
|
1424
|
-
inputSchema:
|
|
1744
|
+
inputSchema: inputSchema45,
|
|
1425
1745
|
annotations: { readOnlyHint: true },
|
|
1426
1746
|
handler: async (args, ctx) => {
|
|
1427
1747
|
return await httpGet(
|
|
@@ -1432,16 +1752,16 @@ var getListingCopyGenerationStatusTool = {
|
|
|
1432
1752
|
};
|
|
1433
1753
|
|
|
1434
1754
|
// src/tools/list-seller-profiles.ts
|
|
1435
|
-
import { z as
|
|
1436
|
-
var
|
|
1437
|
-
currentPage:
|
|
1438
|
-
pageSize:
|
|
1755
|
+
import { z as z44 } from "zod";
|
|
1756
|
+
var inputSchema46 = {
|
|
1757
|
+
currentPage: z44.number().int().min(1).optional().describe("Page number, 1-indexed. Defaults to 1."),
|
|
1758
|
+
pageSize: z44.number().int().min(1).max(50).optional().describe("Items per page (max 50). Defaults to 20.")
|
|
1439
1759
|
};
|
|
1440
1760
|
var listSellerProfilesTool = {
|
|
1441
1761
|
name: "list_seller_profiles",
|
|
1442
1762
|
title: "List Connected Amazon Seller Profiles",
|
|
1443
1763
|
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).',
|
|
1444
|
-
inputSchema:
|
|
1764
|
+
inputSchema: inputSchema46,
|
|
1445
1765
|
annotations: { readOnlyHint: true },
|
|
1446
1766
|
handler: async (args, ctx) => {
|
|
1447
1767
|
return await httpGet(
|
|
@@ -1456,15 +1776,15 @@ var listSellerProfilesTool = {
|
|
|
1456
1776
|
};
|
|
1457
1777
|
|
|
1458
1778
|
// src/tools/get-seller-catalog.ts
|
|
1459
|
-
import { z as
|
|
1779
|
+
import { z as z46 } from "zod";
|
|
1460
1780
|
|
|
1461
1781
|
// src/tools/seller-scoped.ts
|
|
1462
|
-
import { z as
|
|
1782
|
+
import { z as z45 } from "zod";
|
|
1463
1783
|
var sellerScopedInputSchema = {
|
|
1464
|
-
sellerId:
|
|
1784
|
+
sellerId: z45.string().min(1).describe(
|
|
1465
1785
|
"Amazon seller account ID. Get it from `list_seller_profiles`, or find it on the Connections page at https://2.datadive.tools."
|
|
1466
1786
|
),
|
|
1467
|
-
marketplace:
|
|
1787
|
+
marketplace: z45.enum(SUPPORTED_MARKETPLACES).describe('Amazon marketplace code, e.g. "com" for amazon.com or "co.uk" for amazon.co.uk.')
|
|
1468
1788
|
};
|
|
1469
1789
|
function sellerMarketplacePath(sellerId, marketplace, suffix) {
|
|
1470
1790
|
return `/v1/seller_profiles/${encodeURIComponent(sellerId)}/marketplaces/${encodeURIComponent(
|
|
@@ -1473,19 +1793,19 @@ function sellerMarketplacePath(sellerId, marketplace, suffix) {
|
|
|
1473
1793
|
}
|
|
1474
1794
|
|
|
1475
1795
|
// src/tools/get-seller-catalog.ts
|
|
1476
|
-
var
|
|
1796
|
+
var inputSchema47 = {
|
|
1477
1797
|
...sellerScopedInputSchema,
|
|
1478
|
-
search:
|
|
1479
|
-
brand:
|
|
1480
|
-
status:
|
|
1481
|
-
currentPage:
|
|
1482
|
-
pageSize:
|
|
1798
|
+
search: z46.string().min(1).optional().describe("Case-insensitive partial match on product title or brand."),
|
|
1799
|
+
brand: z46.string().min(1).optional().describe("Case-insensitive partial match on brand name."),
|
|
1800
|
+
status: z46.enum(CATALOG_STATUSES).optional().describe('Listing status filter. "Active" (default) or "all".'),
|
|
1801
|
+
currentPage: z46.number().int().min(1).optional().describe("Page number, 1-indexed. Defaults to 1."),
|
|
1802
|
+
pageSize: z46.number().int().min(1).max(50).optional().describe("Items per page (max 50). Defaults to 20.")
|
|
1483
1803
|
};
|
|
1484
1804
|
var getSellerCatalogTool = {
|
|
1485
1805
|
name: "get_seller_catalog",
|
|
1486
1806
|
title: "List Catalog ASINs for a Seller",
|
|
1487
1807
|
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).",
|
|
1488
|
-
inputSchema:
|
|
1808
|
+
inputSchema: inputSchema47,
|
|
1489
1809
|
annotations: { readOnlyHint: true },
|
|
1490
1810
|
handler: async (args, ctx) => {
|
|
1491
1811
|
return await httpGet(
|
|
@@ -1503,28 +1823,28 @@ var getSellerCatalogTool = {
|
|
|
1503
1823
|
};
|
|
1504
1824
|
|
|
1505
1825
|
// src/tools/get-seller-listing-changes.ts
|
|
1506
|
-
import { z as
|
|
1507
|
-
var
|
|
1826
|
+
import { z as z47 } from "zod";
|
|
1827
|
+
var inputSchema48 = {
|
|
1508
1828
|
...sellerScopedInputSchema,
|
|
1509
|
-
types:
|
|
1510
|
-
asin:
|
|
1511
|
-
brand:
|
|
1512
|
-
search:
|
|
1513
|
-
startDate:
|
|
1514
|
-
endDate:
|
|
1515
|
-
sortBy:
|
|
1516
|
-
sortOrder:
|
|
1517
|
-
includeCorrelations:
|
|
1829
|
+
types: z47.array(z47.enum(LISTING_CHANGE_TYPES)).min(1).optional().describe('Filter to specific change types. Omit for all. Any of "Price", "Content", "Image".'),
|
|
1830
|
+
asin: z47.string().min(1).optional().describe("Filter to an exact ASIN or parent ASIN."),
|
|
1831
|
+
brand: z47.string().min(1).optional().describe("Case-insensitive partial match on brand name."),
|
|
1832
|
+
search: z47.string().min(1).optional().describe("Case-insensitive partial match on product title or brand."),
|
|
1833
|
+
startDate: z47.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."),
|
|
1834
|
+
endDate: z47.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."),
|
|
1835
|
+
sortBy: z47.enum(LISTING_CHANGE_SORT_BY).optional().describe('Sort field. "date" (default) or "type".'),
|
|
1836
|
+
sortOrder: z47.enum(SORT_ORDER).optional().describe('Sort direction. "DESC" (default) or "ASC".'),
|
|
1837
|
+
includeCorrelations: z47.boolean().optional().describe(
|
|
1518
1838
|
"When true, include the ranking/conversion `correlation` object per change (CVR and search-term movement before vs after). Defaults to false."
|
|
1519
1839
|
),
|
|
1520
|
-
currentPage:
|
|
1521
|
-
pageSize:
|
|
1840
|
+
currentPage: z47.number().int().min(1).optional().describe("Page number, 1-indexed. Defaults to 1."),
|
|
1841
|
+
pageSize: z47.number().int().min(1).max(50).optional().describe("Items per page (max 50). Defaults to 20.")
|
|
1522
1842
|
};
|
|
1523
1843
|
var getSellerListingChangesTool = {
|
|
1524
1844
|
name: "get_seller_listing_changes",
|
|
1525
1845
|
title: "List Listing Changes for a Seller",
|
|
1526
1846
|
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).",
|
|
1527
|
-
inputSchema:
|
|
1847
|
+
inputSchema: inputSchema48,
|
|
1528
1848
|
annotations: { readOnlyHint: true },
|
|
1529
1849
|
handler: async (args, ctx) => {
|
|
1530
1850
|
return await httpGet(
|
|
@@ -1549,40 +1869,40 @@ var getSellerListingChangesTool = {
|
|
|
1549
1869
|
};
|
|
1550
1870
|
|
|
1551
1871
|
// src/tools/list-ppc-campaigns.ts
|
|
1552
|
-
import { z as
|
|
1872
|
+
import { z as z48 } from "zod";
|
|
1553
1873
|
var PPC_CAMPAIGNS_MAX_WINDOW_DAYS = 90;
|
|
1554
1874
|
var PPC_CAMPAIGNS_MAX_PAGE_SIZE = 50;
|
|
1555
|
-
var
|
|
1875
|
+
var inputSchema49 = {
|
|
1556
1876
|
...sellerScopedInputSchema,
|
|
1557
|
-
startDate:
|
|
1877
|
+
startDate: z48.string().regex(
|
|
1558
1878
|
ISO_8601,
|
|
1559
1879
|
"startDate must be an ISO-8601 date or timestamp (e.g. 2026-05-01 or 2026-05-01T00:00:00Z)"
|
|
1560
1880
|
).optional().describe("Start of the reporting window, ISO-8601. Defaults to 30 days before endDate."),
|
|
1561
|
-
endDate:
|
|
1881
|
+
endDate: z48.string().regex(
|
|
1562
1882
|
ISO_8601,
|
|
1563
1883
|
"endDate must be an ISO-8601 date or timestamp (e.g. 2026-05-31 or 2026-05-31T23:59:59Z)"
|
|
1564
1884
|
).optional().describe(
|
|
1565
1885
|
`End of the reporting window, ISO-8601. Defaults to now. At most ${PPC_CAMPAIGNS_MAX_WINDOW_DAYS} days after startDate.`
|
|
1566
1886
|
),
|
|
1567
|
-
asin:
|
|
1568
|
-
parentAsin:
|
|
1569
|
-
search:
|
|
1570
|
-
includePlacements:
|
|
1887
|
+
asin: z48.string().min(1).optional().describe("Only campaigns advertising this ASIN. Cannot be combined with parentAsin."),
|
|
1888
|
+
parentAsin: z48.string().min(1).optional().describe("Only campaigns advertising any ASIN of this variation family. Cannot be combined with asin."),
|
|
1889
|
+
search: z48.string().min(3).max(200).optional().describe("Partial match on campaign name or advertised ASIN. At least 3 characters."),
|
|
1890
|
+
includePlacements: z48.boolean().optional().describe(
|
|
1571
1891
|
"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."
|
|
1572
1892
|
),
|
|
1573
|
-
sortBy:
|
|
1893
|
+
sortBy: z48.enum([...PPC_CAMPAIGN_BASE_SORT_BY, ...PPC_CAMPAIGN_PLACEMENT_SORT_BY]).optional().describe(
|
|
1574
1894
|
`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.`
|
|
1575
1895
|
),
|
|
1576
|
-
sortOrder:
|
|
1577
|
-
state:
|
|
1578
|
-
currentPage:
|
|
1579
|
-
pageSize:
|
|
1896
|
+
sortOrder: z48.enum(SORT_ORDER).optional().describe('Sort direction. "ASC" (default) or "DESC".'),
|
|
1897
|
+
state: z48.enum(PPC_CAMPAIGN_STATES).optional().describe("Only ENABLED or only PAUSED campaigns. Omit for both."),
|
|
1898
|
+
currentPage: z48.number().int().min(1).optional().describe("Page number, 1-indexed. Defaults to 1."),
|
|
1899
|
+
pageSize: z48.number().int().min(1).max(PPC_CAMPAIGNS_MAX_PAGE_SIZE).optional().describe(`Campaigns per page (max ${PPC_CAMPAIGNS_MAX_PAGE_SIZE}). Defaults to 20.`)
|
|
1580
1900
|
};
|
|
1581
1901
|
var listPpcCampaignsTool = {
|
|
1582
1902
|
name: "list_ppc_campaigns",
|
|
1583
1903
|
title: "List PPC Campaigns for a Seller",
|
|
1584
1904
|
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\`.`,
|
|
1585
|
-
inputSchema:
|
|
1905
|
+
inputSchema: inputSchema49,
|
|
1586
1906
|
annotations: { readOnlyHint: true },
|
|
1587
1907
|
handler: async (args, ctx) => {
|
|
1588
1908
|
if (args.asin && args.parentAsin) {
|
|
@@ -1613,13 +1933,13 @@ var listPpcCampaignsTool = {
|
|
|
1613
1933
|
};
|
|
1614
1934
|
|
|
1615
1935
|
// src/tools/get-asin-inventory-distribution.ts
|
|
1616
|
-
import { z as
|
|
1617
|
-
var
|
|
1618
|
-
sellerId:
|
|
1936
|
+
import { z as z49 } from "zod";
|
|
1937
|
+
var inputSchema50 = {
|
|
1938
|
+
sellerId: z49.string().min(1).describe(
|
|
1619
1939
|
"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."
|
|
1620
1940
|
),
|
|
1621
|
-
marketplace:
|
|
1622
|
-
asin:
|
|
1941
|
+
marketplace: z49.enum(SUPPORTED_MARKETPLACES).describe('Amazon marketplace code, e.g. "com" for amazon.com or "co.uk" for amazon.co.uk.'),
|
|
1942
|
+
asin: z49.string().regex(/^[A-Z0-9]{10}$/, "ASIN must be 10 uppercase alphanumeric characters (e.g. B08XYZ1234).").describe(
|
|
1623
1943
|
"Amazon Standard Identification Number (10 uppercase alphanumeric characters, e.g. B08XYZ1234)."
|
|
1624
1944
|
)
|
|
1625
1945
|
};
|
|
@@ -1627,7 +1947,7 @@ var getAsinInventoryDistributionTool = {
|
|
|
1627
1947
|
name: "get_asin_inventory_distribution",
|
|
1628
1948
|
title: "Get Inventory Metrics and Fulfillment-Center Distribution for an ASIN",
|
|
1629
1949
|
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.",
|
|
1630
|
-
inputSchema:
|
|
1950
|
+
inputSchema: inputSchema50,
|
|
1631
1951
|
annotations: { readOnlyHint: true },
|
|
1632
1952
|
handler: async (args, ctx) => {
|
|
1633
1953
|
return await httpGet(
|
|
@@ -1640,18 +1960,18 @@ var getAsinInventoryDistributionTool = {
|
|
|
1640
1960
|
};
|
|
1641
1961
|
|
|
1642
1962
|
// src/tools/alert-query.ts
|
|
1643
|
-
import { z as
|
|
1963
|
+
import { z as z50 } from "zod";
|
|
1644
1964
|
var alertsQueryInputSchema = {
|
|
1645
|
-
sellerId:
|
|
1646
|
-
marketplace:
|
|
1647
|
-
status:
|
|
1965
|
+
sellerId: z50.string().min(1).max(50).optional().describe('Filter to a single connected Amazon seller account ID, e.g. "A1B2C3D4E5".'),
|
|
1966
|
+
marketplace: z50.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.'),
|
|
1967
|
+
status: z50.enum(ALERT_STATUSES).optional().describe(
|
|
1648
1968
|
"Lifecycle filter. `active` (default) returns unresolved alerts, `resolved` returns resolved alerts, `all` returns both. Dismissed alerts are never returned."
|
|
1649
1969
|
),
|
|
1650
|
-
updatedSince:
|
|
1970
|
+
updatedSince: z50.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(
|
|
1651
1971
|
"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."
|
|
1652
1972
|
),
|
|
1653
|
-
currentPage:
|
|
1654
|
-
pageSize:
|
|
1973
|
+
currentPage: z50.number().int().min(1).optional().describe("Page number, 1-indexed. Defaults to 1."),
|
|
1974
|
+
pageSize: z50.number().int().min(1).max(50).optional().describe("Items per page (max 50). Defaults to 20.")
|
|
1655
1975
|
};
|
|
1656
1976
|
|
|
1657
1977
|
// src/tools/list-indexing-issue-alerts.ts
|
|
@@ -1701,12 +2021,12 @@ var listBlindSpendAlertsTool = {
|
|
|
1701
2021
|
};
|
|
1702
2022
|
|
|
1703
2023
|
// src/tools/get-quota.ts
|
|
1704
|
-
var
|
|
2024
|
+
var inputSchema51 = {};
|
|
1705
2025
|
var getQuotaTool = {
|
|
1706
2026
|
name: "get_quota",
|
|
1707
2027
|
title: "Get Quota Usage",
|
|
1708
2028
|
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.",
|
|
1709
|
-
inputSchema:
|
|
2029
|
+
inputSchema: inputSchema51,
|
|
1710
2030
|
annotations: { readOnlyHint: true },
|
|
1711
2031
|
handler: async (_args, ctx) => {
|
|
1712
2032
|
return await httpGet({ config: ctx.config, toolName: "get_quota" }, "/v1/quota");
|
|
@@ -1714,20 +2034,20 @@ var getQuotaTool = {
|
|
|
1714
2034
|
};
|
|
1715
2035
|
|
|
1716
2036
|
// src/tools/list-usage.ts
|
|
1717
|
-
import { z as
|
|
1718
|
-
var
|
|
1719
|
-
type:
|
|
1720
|
-
search:
|
|
1721
|
-
startDate:
|
|
1722
|
-
endDate:
|
|
1723
|
-
currentPage:
|
|
1724
|
-
pageSize:
|
|
2037
|
+
import { z as z51 } from "zod";
|
|
2038
|
+
var inputSchema52 = {
|
|
2039
|
+
type: z51.enum(BILLABLE_FEATURE_TYPES).optional().describe("Filter to a single billable feature type. Omit to return all types."),
|
|
2040
|
+
search: z51.string().min(1).optional().describe("Case-insensitive partial match on the user's name or email."),
|
|
2041
|
+
startDate: z51.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."),
|
|
2042
|
+
endDate: z51.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."),
|
|
2043
|
+
currentPage: z51.number().int().min(1).optional().describe("Page number, 1-indexed. Defaults to 1."),
|
|
2044
|
+
pageSize: z51.number().int().min(1).max(200).optional().describe("Items per page (max 200). Defaults to 50.")
|
|
1725
2045
|
};
|
|
1726
2046
|
var listUsageTool = {
|
|
1727
2047
|
name: "list_usage",
|
|
1728
2048
|
title: "List Billable Usage Logs",
|
|
1729
2049
|
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).",
|
|
1730
|
-
inputSchema:
|
|
2050
|
+
inputSchema: inputSchema52,
|
|
1731
2051
|
annotations: { readOnlyHint: true },
|
|
1732
2052
|
handler: async (args, ctx) => {
|
|
1733
2053
|
return await httpGet(
|
|
@@ -1748,6 +2068,11 @@ var listUsageTool = {
|
|
|
1748
2068
|
// src/tools/index.ts
|
|
1749
2069
|
var allTools = [
|
|
1750
2070
|
listNichesTool,
|
|
2071
|
+
listFastKeywordsTool,
|
|
2072
|
+
getFastKeywordsTool,
|
|
2073
|
+
getFastKeywordsKeywordsTool,
|
|
2074
|
+
listSpacesTool,
|
|
2075
|
+
listLabelsTool,
|
|
1751
2076
|
getNicheKeywordsTool,
|
|
1752
2077
|
listKeywordExclusionsTool,
|
|
1753
2078
|
getNicheRootsTool,
|
|
@@ -1759,6 +2084,8 @@ var allTools = [
|
|
|
1759
2084
|
getRankRadarPpcDataTool,
|
|
1760
2085
|
listRankRadarTagsTool,
|
|
1761
2086
|
createRankRadarTool,
|
|
2087
|
+
runFastKeywordsTool,
|
|
2088
|
+
refreshFastKeywordsTool,
|
|
1762
2089
|
createNicheDiveTool,
|
|
1763
2090
|
createNicheDiveFromCompetitorsListTool,
|
|
1764
2091
|
rediveNicheTool,
|
|
@@ -1767,6 +2094,10 @@ var allTools = [
|
|
|
1767
2094
|
removeNicheKeywordsTool,
|
|
1768
2095
|
addKeywordExclusionsTool,
|
|
1769
2096
|
removeKeywordExclusionsTool,
|
|
2097
|
+
addNicheSpacesTool,
|
|
2098
|
+
removeNicheSpacesTool,
|
|
2099
|
+
addNicheLabelsTool,
|
|
2100
|
+
removeNicheLabelsTool,
|
|
1770
2101
|
addRankRadarSearchTermsTool,
|
|
1771
2102
|
pauseRankRadarSearchTermsTool,
|
|
1772
2103
|
resumeRankRadarSearchTermsTool,
|
|
@@ -1794,6 +2125,20 @@ var allTools = [
|
|
|
1794
2125
|
|
|
1795
2126
|
// src/whats-new.ts
|
|
1796
2127
|
var WHATS_NEW = [
|
|
2128
|
+
{
|
|
2129
|
+
version: "0.20.1",
|
|
2130
|
+
released: "2026-10-23",
|
|
2131
|
+
items: [
|
|
2132
|
+
"Run Fast Keywords from just an ASIN and read the result (`run_fast_keywords`, `get_fast_keywords`, `get_fast_keywords_keywords`, `list_fast_keywords`, `refresh_fast_keywords`). A run costs 1 Dive token and asks you to confirm first. A finished run can be refreshed once every 24 hours, and `create_niche_dive` can now link a full niche dive to the Fast Keywords run it follows."
|
|
2133
|
+
]
|
|
2134
|
+
},
|
|
2135
|
+
{
|
|
2136
|
+
version: "0.20.0",
|
|
2137
|
+
released: "2026-10-23",
|
|
2138
|
+
items: [
|
|
2139
|
+
"See your Spaces and labels (`list_spaces`, `list_labels`), put a niche into them or take it out (`add_niche_spaces`, `remove_niche_spaces`, `add_niche_labels`, `remove_niche_labels`), and filter `list_niches` and `list_rank_radars` by Space and label. Free. Changing a niche's Spaces and labels is for the organization owner or a team admin; a Space you only have view rights on is refused."
|
|
2140
|
+
]
|
|
2141
|
+
},
|
|
1797
2142
|
{
|
|
1798
2143
|
version: "0.17.1",
|
|
1799
2144
|
released: "2026-10-13",
|