@datadive-tools/mcp 0.12.0 → 0.14.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 CHANGED
@@ -103,7 +103,7 @@ niches, plus pagination metadata. If you don't, see Troubleshooting below.
103
103
  | `get_niche_competitors` | Competitor ASINs, titles, BSR, category and niche statistics (sales, revenue, ratings, opportunity score). |
104
104
  | `get_ranking_juice` | DataDive proprietary ranking-juice metric per competitor (current vs optimized listing). |
105
105
  | `list_rank_radars` | Paginated list of rank radars. Filter by `nicheId`, `searchText` or `status` (`ACTIVE` by default, plus `PAUSED`, `ARCHIVED`, `ALL`). |
106
- | `get_rank_radar_data` | Historical keyword rankings for a rank radar within a `startDate`/`endDate` range. |
106
+ | `get_rank_radar_data` | Historical keyword rankings for a rank radar within a `startDate`/`endDate` range (max 90 days). Paged by keyword: walk `currentPage` while `hasNext` is true, `pageSize` up to 100. |
107
107
  | `create_niche_dive` | **Spends dive tokens.** Starts new niche research from a seed ASIN. Async — returns a `diveId` to poll with `get_dive_status`. Requires `confirm: true`. |
108
108
  | `redive_niche` | **Spends dive tokens.** Refreshes an existing niche with current data — either the same competitors or a newly discovered set. Async — returns a `diveId` to poll with `get_dive_status`. Requires `confirm: true`. |
109
109
  | `get_dive_status` | Poll a dive started by `create_niche_dive` or `redive_niche`: `in_progress`, `success` (carries the `nicheId`), or `error`. |
@@ -146,6 +146,14 @@ assistant to confirm the cost with you first:
146
146
 
147
147
  Check your remaining balance any time with `get_quota`.
148
148
 
149
+ `create_rank_radar` uses that first, unconfirmed call to preview the creation
150
+ against the API, so the note it comes back with can tell you something specific
151
+ rather than only the token cost — for instance that the product family you asked
152
+ for is already tracked by another of your Rank Radars, which would spend Tracked
153
+ Search Terms on it twice. The preview creates nothing and spends nothing, and a
154
+ warning never blocks the creation: two Rank Radars on one family is how you
155
+ track two different sets of search terms and keep separate statistics.
156
+
149
157
  The other write tools — `pause_rank_radar`, `resume_rank_radar` and the three
150
158
  `*_rank_radar_search_terms` tools — need no confirmation. They only move Daily
151
159
  Tracked Keywords capacity, which is freed again when you pause, and each is
package/dist/index.js CHANGED
@@ -144,7 +144,7 @@ function extractMessage(body) {
144
144
  }
145
145
 
146
146
  // package.json
147
- var version = "0.12.0";
147
+ var version = "0.14.0";
148
148
 
149
149
  // src/http/client.ts
150
150
  var PKG_VERSION = version;
@@ -462,22 +462,33 @@ var listRankRadarsTool = {
462
462
  // src/tools/get-rank-radar-data.ts
463
463
  import { z as z7 } from "zod";
464
464
  var ISO_DATE = /^\d{4}-\d{2}-\d{2}$/;
465
+ var RANK_RADAR_KEYWORDS_MAX_PAGE_SIZE = 100;
466
+ var RANK_RADAR_MAX_DATE_RANGE_DAYS = 90;
465
467
  var inputSchema7 = {
466
468
  rankRadarId: z7.string().uuid().describe("The Rank Radar UUID (from `list_rank_radars`)."),
467
469
  startDate: z7.string().regex(ISO_DATE, "startDate must be in yyyy-mm-dd format").describe("Start date for the ranking data range, yyyy-mm-dd (e.g. 2024-03-26)."),
468
- endDate: z7.string().regex(ISO_DATE, "endDate must be in yyyy-mm-dd format").describe("End date for the ranking data range, yyyy-mm-dd (e.g. 2024-04-26). Must be on or after startDate.")
470
+ endDate: z7.string().regex(ISO_DATE, "endDate must be in yyyy-mm-dd format").describe(
471
+ `End date for the ranking data 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.`
472
+ ),
473
+ currentPage: z7.number().int().min(1).optional().describe("Page of keywords, 1-indexed. Defaults to 1."),
474
+ pageSize: z7.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.`)
469
475
  };
470
476
  var getRankRadarDataTool = {
471
477
  name: "get_rank_radar_data",
472
478
  title: "Get Keyword Rankings for a Rank Radar",
473
- description: "Use this to analyze keyword ranking trends over time. Requires startDate and endDate (yyyy-mm-dd). Retrieves historical keyword ranking data for the specified Rank Radar within the date range. Returns an array of tracked keywords; each has id, keyword, searchVolume, ranks (per-day { date, organicRank, impressionRank }), and any highlight annotations. Use after `list_rank_radars` to discover a `rankRadarId`.",
479
+ 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. Results are paged by keyword: \`data\` holds one page of tracked keywords, and the response carries currentPage, pageSize, total, lastPage, hasNext and hasPrev. \`total\` is the Rank Radar's 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. Each keyword has id, keyword, searchVolume, relevancy, ranks (per-day { date, organicRank, sponsoredRank, impressionRank }), and any highlight annotations. Paused keywords are not included. Use after \`list_rank_radars\` to discover a \`rankRadarId\`.`,
474
480
  inputSchema: inputSchema7,
475
481
  annotations: { readOnlyHint: true },
476
482
  handler: async (args, ctx) => {
477
483
  return await httpGet(
478
484
  { config: ctx.config, toolName: "get_rank_radar_data" },
479
485
  `/v1/niches/rank-radars/${encodeURIComponent(args.rankRadarId)}`,
480
- { startDate: args.startDate, endDate: args.endDate }
486
+ {
487
+ startDate: args.startDate,
488
+ endDate: args.endDate,
489
+ currentPage: args.currentPage,
490
+ pageSize: args.pageSize
491
+ }
481
492
  );
482
493
  }
483
494
  };
@@ -486,16 +497,24 @@ var getRankRadarDataTool = {
486
497
  import { z as z8 } from "zod";
487
498
 
488
499
  // src/tools/confirm.ts
489
- function requireConfirmation(confirm, ctx, costNote) {
490
- if (confirm === true || ctx.config.autoConfirmWrites) return null;
500
+ function needsConfirmation(confirm, ctx) {
501
+ return !(confirm === true || ctx.config.autoConfirmWrites);
502
+ }
503
+ function confirmationRequired(costNote, warnings) {
491
504
  return {
492
505
  status: "confirmation_required",
493
506
  message: "This action spends billable tokens and cannot be undone. Confirm the cost with the user, then call again with `confirm: true` to proceed.",
494
- costNote
507
+ costNote,
508
+ ...warnings && warnings.length > 0 ? { warnings } : {}
495
509
  };
496
510
  }
511
+ function requireConfirmation(confirm, ctx, costNote) {
512
+ if (!needsConfirmation(confirm, ctx)) return null;
513
+ return confirmationRequired(costNote);
514
+ }
497
515
 
498
516
  // src/tools/create-rank-radar.ts
517
+ var PATH = "/v1/niches/rank-radars";
499
518
  var inputSchema8 = {
500
519
  asin: z8.string().min(1).describe("The ASIN to track keyword rankings for."),
501
520
  numberOfKeywords: z8.number().int().min(1).describe("How many keywords to track (minimum 1). More keywords = broader tracking but more Search Term tokens."),
@@ -507,28 +526,28 @@ var inputSchema8 = {
507
526
  var createRankRadarTool = {
508
527
  name: "create_rank_radar",
509
528
  title: "Create a Rank Radar",
510
- 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. Returns the new `rankRadarId`; read its data later with `get_rank_radar_data`. Requires `asin`, `numberOfKeywords` (min 1), and a `nicheId` from `list_niches`.",
529
+ 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`.",
511
530
  inputSchema: inputSchema8,
512
531
  // destructiveHint is technically "additive" here (this creates a new Rank Radar, it
513
532
  // doesn't destroy data), but we flag it destructive so clients prompt before the
514
533
  // irreversible token spend. The confirm gate enforces this server-side regardless.
515
534
  annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
516
535
  handler: async (args, ctx) => {
517
- const pending = requireConfirmation(
518
- args.confirm,
519
- ctx,
520
- `Creating this Rank Radar consumes Search Term tokens \u2014 one per keyword tracked (${args.numberOfKeywords} requested).`
521
- );
522
- if (pending) return pending;
523
- return await httpPost(
524
- { config: ctx.config, toolName: "create_rank_radar" },
525
- "/v1/niches/rank-radars",
526
- {
527
- asin: args.asin,
528
- numberOfKeywords: args.numberOfKeywords,
529
- nicheId: args.nicheId
530
- }
531
- );
536
+ const request = { config: ctx.config, toolName: "create_rank_radar" };
537
+ const body = {
538
+ asin: args.asin,
539
+ numberOfKeywords: args.numberOfKeywords,
540
+ nicheId: args.nicheId
541
+ };
542
+ if (needsConfirmation(args.confirm, ctx)) {
543
+ const preview = await httpPost(request, PATH, { ...body, dryRun: true });
544
+ if (preview.rankRadarId != null) return preview;
545
+ return confirmationRequired(
546
+ `Creating this Rank Radar consumes Search Term tokens \u2014 one per keyword tracked (${args.numberOfKeywords} requested).`,
547
+ preview.warnings
548
+ );
549
+ }
550
+ return await httpPost(request, PATH, body);
532
551
  }
533
552
  };
534
553