@datadive-tools/mcp 0.13.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/lib.js CHANGED
@@ -139,7 +139,7 @@ function extractMessage(body) {
139
139
  }
140
140
 
141
141
  // package.json
142
- var version = "0.13.0";
142
+ var version = "0.15.0";
143
143
 
144
144
  // src/http/client.ts
145
145
  var PKG_VERSION = version;
@@ -403,6 +403,33 @@ var SUPPORTED_MARKETPLACES = [
403
403
  ];
404
404
  var RANK_RADAR_STATES = ["ACTIVE", "PAUSED", "ARCHIVED"];
405
405
  var RANK_RADAR_STATUSES = [...RANK_RADAR_STATES, "ALL"];
406
+ var PPC_CAMPAIGN_BASE_SORT_BY = [
407
+ "name",
408
+ "state",
409
+ "bidStrategy",
410
+ "budget",
411
+ "impressions",
412
+ "clicks",
413
+ "ctr",
414
+ "cpc",
415
+ "spend",
416
+ "unitsSold",
417
+ "sales",
418
+ "acos",
419
+ "cvr",
420
+ "tosImpressionShare"
421
+ ];
422
+ var PPC_CAMPAIGN_PLACEMENT_SORT_BY = [
423
+ ...["tos", "ros", "pp", "off"].flatMap(
424
+ (p) => ["Impressions", "Clicks", "Spend", "Sales", "UnitsSold", "Ctr", "Cpc", "Acos"].map(
425
+ (m) => `${p}${m}`
426
+ )
427
+ ),
428
+ "tosBidAdjustment",
429
+ "rosBidAdjustment",
430
+ "ppBidAdjustment"
431
+ ];
432
+ var PPC_CAMPAIGN_STATES = ["ENABLED", "PAUSED"];
406
433
  var ALERT_STATUSES = ["active", "resolved", "all"];
407
434
  var BILLABLE_FEATURE_TYPES = [
408
435
  "DIVED_ASINS",
@@ -454,31 +481,92 @@ var listRankRadarsTool = {
454
481
  }
455
482
  };
456
483
 
457
- // src/tools/get-rank-radar-data.ts
484
+ // src/tools/rank-radar-keyword-query.ts
458
485
  import { z as z7 } from "zod";
459
486
  var ISO_DATE = /^\d{4}-\d{2}-\d{2}$/;
460
- var inputSchema7 = {
487
+ var RANK_RADAR_KEYWORDS_MAX_PAGE_SIZE = 100;
488
+ var RANK_RADAR_MAX_DATE_RANGE_DAYS = 90;
489
+ var rankRadarKeywordQueryInputSchema = {
461
490
  rankRadarId: z7.string().uuid().describe("The Rank Radar UUID (from `list_rank_radars`)."),
462
- 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)."),
463
- 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.")
491
+ startDate: z7.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)."),
492
+ endDate: z7.string().regex(ISO_DATE, "endDate must be in yyyy-mm-dd format").describe(
493
+ `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.`
494
+ ),
495
+ currentPage: z7.number().int().min(1).optional().describe("Page of keywords, 1-indexed. Defaults to 1."),
496
+ 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.`)
464
497
  };
498
+ 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.`;
499
+ function rankRadarPath(rankRadarId, suffix = "") {
500
+ return `/v1/niches/rank-radars/${encodeURIComponent(rankRadarId)}${suffix ? `/${suffix}` : ""}`;
501
+ }
502
+ function rankRadarKeywordQuery(args) {
503
+ return {
504
+ startDate: args.startDate,
505
+ endDate: args.endDate,
506
+ currentPage: args.currentPage,
507
+ pageSize: args.pageSize
508
+ };
509
+ }
510
+
511
+ // src/tools/get-rank-radar-data.ts
512
+ var inputSchema7 = rankRadarKeywordQueryInputSchema;
465
513
  var getRankRadarDataTool = {
466
514
  name: "get_rank_radar_data",
467
515
  title: "Get Keyword Rankings for a Rank Radar",
468
- 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`.",
516
+ 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. ${RANK_RADAR_KEYWORD_PAGING_NOTE} Each keyword has id, keyword, searchVolume, relevancy, ranks (per-day { date, organicRank, sponsoredRank, impressionRank }), and any highlight annotations. A rank of 101 means the product was not in the top 100 results that day. For PPC metrics use \`get_rank_radar_ppc_data\`; for Search Query Performance use \`get_rank_radar_sqp_data\`. Use after \`list_rank_radars\` to discover a \`rankRadarId\`.`,
469
517
  inputSchema: inputSchema7,
470
518
  annotations: { readOnlyHint: true },
471
519
  handler: async (args, ctx) => {
472
520
  return await httpGet(
473
521
  { config: ctx.config, toolName: "get_rank_radar_data" },
474
- `/v1/niches/rank-radars/${encodeURIComponent(args.rankRadarId)}`,
475
- { startDate: args.startDate, endDate: args.endDate }
522
+ rankRadarPath(args.rankRadarId),
523
+ rankRadarKeywordQuery(args)
476
524
  );
477
525
  }
478
526
  };
479
527
 
480
- // src/tools/create-rank-radar.ts
528
+ // src/tools/get-rank-radar-sqp-data.ts
529
+ var inputSchema8 = rankRadarKeywordQueryInputSchema;
530
+ var getRankRadarSqpDataTool = {
531
+ name: "get_rank_radar_sqp_data",
532
+ title: "Get Search Query Performance for a Rank Radar",
533
+ 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\`.`,
534
+ inputSchema: inputSchema8,
535
+ annotations: { readOnlyHint: true },
536
+ handler: async (args, ctx) => {
537
+ return await httpGet(
538
+ { config: ctx.config, toolName: "get_rank_radar_sqp_data" },
539
+ rankRadarPath(args.rankRadarId, "sqp"),
540
+ rankRadarKeywordQuery(args)
541
+ );
542
+ }
543
+ };
544
+
545
+ // src/tools/get-rank-radar-ppc-data.ts
481
546
  import { z as z8 } from "zod";
547
+ var inputSchema9 = {
548
+ ...rankRadarKeywordQueryInputSchema,
549
+ includeCampaigns: z8.boolean().optional().describe(
550
+ "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."
551
+ )
552
+ };
553
+ var getRankRadarPpcDataTool = {
554
+ name: "get_rank_radar_ppc_data",
555
+ title: "Get PPC Data for a Rank Radar",
556
+ 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\`.`,
557
+ inputSchema: inputSchema9,
558
+ annotations: { readOnlyHint: true },
559
+ handler: async (args, ctx) => {
560
+ return await httpGet(
561
+ { config: ctx.config, toolName: "get_rank_radar_ppc_data" },
562
+ rankRadarPath(args.rankRadarId, "ppc"),
563
+ { ...rankRadarKeywordQuery(args), includeCampaigns: args.includeCampaigns }
564
+ );
565
+ }
566
+ };
567
+
568
+ // src/tools/create-rank-radar.ts
569
+ import { z as z9 } from "zod";
482
570
 
483
571
  // src/tools/confirm.ts
484
572
  function needsConfirmation(confirm, ctx) {
@@ -499,11 +587,11 @@ function requireConfirmation(confirm, ctx, costNote) {
499
587
 
500
588
  // src/tools/create-rank-radar.ts
501
589
  var PATH = "/v1/niches/rank-radars";
502
- var inputSchema8 = {
503
- asin: z8.string().min(1).describe("The ASIN to track keyword rankings for."),
504
- numberOfKeywords: z8.number().int().min(1).describe("How many keywords to track (minimum 1). More keywords = broader tracking but more Search Term tokens."),
505
- nicheId: z8.string().min(1).describe("The niche this Rank Radar belongs to. Get one from `list_niches`."),
506
- confirm: z8.boolean().optional().describe(
590
+ var inputSchema10 = {
591
+ asin: z9.string().min(1).describe("The ASIN to track keyword rankings for."),
592
+ numberOfKeywords: z9.number().int().min(1).describe("How many keywords to track (minimum 1). More keywords = broader tracking but more Search Term tokens."),
593
+ nicheId: z9.string().min(1).describe("The niche this Rank Radar belongs to. Get one from `list_niches`."),
594
+ confirm: z9.boolean().optional().describe(
507
595
  "Must be true to proceed \u2014 creating a Rank Radar spends Search Term tokens. Confirm the cost with the user first."
508
596
  )
509
597
  };
@@ -511,7 +599,7 @@ var createRankRadarTool = {
511
599
  name: "create_rank_radar",
512
600
  title: "Create a Rank Radar",
513
601
  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`.",
514
- inputSchema: inputSchema8,
602
+ inputSchema: inputSchema10,
515
603
  // destructiveHint is technically "additive" here (this creates a new Rank Radar, it
516
604
  // doesn't destroy data), but we flag it destructive so clients prompt before the
517
605
  // irreversible token spend. The confirm gate enforces this server-side regardless.
@@ -536,20 +624,20 @@ var createRankRadarTool = {
536
624
  };
537
625
 
538
626
  // src/tools/create-niche-dive.ts
539
- import { z as z9 } from "zod";
540
- var inputSchema9 = {
541
- marketplace: z9.enum(SUPPORTED_MARKETPLACES).describe(
627
+ import { z as z10 } from "zod";
628
+ var inputSchema11 = {
629
+ marketplace: z10.enum(SUPPORTED_MARKETPLACES).describe(
542
630
  "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`)."
543
631
  ),
544
- asin: z9.string().min(1).describe("The seed ASIN to start the dive from (e.g. B08N5WRWNW). Competitors are built around it."),
545
- numberOfCompetitors: z9.number().int().min(2).describe("How many competitors to analyze (minimum 2). More competitors = deeper niche but more dive tokens."),
546
- confirm: z9.boolean().optional().describe("Must be true to proceed \u2014 creating a dive spends dive tokens. Confirm the cost with the user first.")
632
+ asin: z10.string().min(1).describe("The seed ASIN to start the dive from (e.g. B08N5WRWNW). Competitors are built around it."),
633
+ numberOfCompetitors: z10.number().int().min(2).describe("How many competitors to analyze (minimum 2). More competitors = deeper niche but more dive tokens."),
634
+ confirm: z10.boolean().optional().describe("Must be true to proceed \u2014 creating a dive spends dive tokens. Confirm the cost with the user first.")
547
635
  };
548
636
  var createNicheDiveTool = {
549
637
  name: "create_niche_dive",
550
638
  title: "Create a Niche Dive",
551
639
  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).",
552
- inputSchema: inputSchema9,
640
+ inputSchema: inputSchema11,
553
641
  // destructiveHint is technically "additive" here (a dive creates a new niche, it
554
642
  // doesn't destroy data), but we flag it destructive so clients prompt before the
555
643
  // irreversible token spend. The confirm gate enforces this server-side regardless.
@@ -574,23 +662,23 @@ var createNicheDiveTool = {
574
662
  };
575
663
 
576
664
  // src/tools/redive-niche.ts
577
- import { z as z10 } from "zod";
578
- var inputSchema10 = {
579
- nicheId: z10.string().min(1).describe("The niche to re-dive. Get one from `list_niches`."),
580
- mode: z10.enum(["same_competitors", "discover"]).describe(
665
+ import { z as z11 } from "zod";
666
+ var inputSchema12 = {
667
+ nicheId: z11.string().min(1).describe("The niche to re-dive. Get one from `list_niches`."),
668
+ mode: z11.enum(["same_competitors", "discover"]).describe(
581
669
  "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."
582
670
  ),
583
- numberOfCompetitors: z10.number().int().min(2).optional().describe(
671
+ numberOfCompetitors: z11.number().int().min(2).optional().describe(
584
672
  "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."
585
673
  ),
586
- heroAsin: z10.string().min(1).optional().describe(
674
+ heroAsin: z11.string().min(1).optional().describe(
587
675
  "`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."
588
676
  ),
589
- lockedAsins: z10.array(z10.string().min(1)).optional().describe(
677
+ lockedAsins: z11.array(z11.string().min(1)).optional().describe(
590
678
  "`discover` mode only. ASINs that must stay in the competitor set regardless of what discovery finds. Cannot exceed `numberOfCompetitors`, and cannot overlap `excludedAsins`."
591
679
  ),
592
- excludedAsins: z10.array(z10.string().min(1)).optional().describe("`discover` mode only. ASINs discovery must never select."),
593
- confirm: z10.boolean().optional().describe(
680
+ excludedAsins: z11.array(z11.string().min(1)).optional().describe("`discover` mode only. ASINs discovery must never select."),
681
+ confirm: z11.boolean().optional().describe(
594
682
  "Must be true to proceed \u2014 a re-dive spends dive tokens. Confirm the cost with the user first."
595
683
  )
596
684
  };
@@ -598,7 +686,7 @@ var rediveNicheTool = {
598
686
  name: "redive_niche",
599
687
  title: "Re-dive an Existing Niche",
600
688
  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.",
601
- inputSchema: inputSchema10,
689
+ inputSchema: inputSchema12,
602
690
  // Same reasoning as create_niche_dive: a re-dive is additive rather than destructive, but the
603
691
  // irreversible token spend is what clients need to prompt on. The confirm gate enforces it regardless.
604
692
  annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
@@ -631,15 +719,15 @@ var rediveNicheTool = {
631
719
  };
632
720
 
633
721
  // src/tools/get-dive-status.ts
634
- import { z as z11 } from "zod";
635
- var inputSchema11 = {
636
- diveId: z11.string().min(1).describe("The dive identifier returned by `create_niche_dive` or `redive_niche`.")
722
+ import { z as z12 } from "zod";
723
+ var inputSchema13 = {
724
+ diveId: z12.string().min(1).describe("The dive identifier returned by `create_niche_dive` or `redive_niche`.")
637
725
  };
638
726
  var getDiveStatusTool = {
639
727
  name: "get_dive_status",
640
728
  title: "Get Niche Dive Status",
641
729
  description: "Use this to poll a niche dive started with `create_niche_dive` or `redive_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.",
642
- inputSchema: inputSchema11,
730
+ inputSchema: inputSchema13,
643
731
  annotations: { readOnlyHint: true },
644
732
  handler: async (args, ctx) => {
645
733
  return await httpGet(
@@ -650,10 +738,10 @@ var getDiveStatusTool = {
650
738
  };
651
739
 
652
740
  // src/tools/delete-niche.ts
653
- import { z as z12 } from "zod";
654
- var inputSchema12 = {
655
- nicheId: z12.string().min(1).describe("The niche to delete. Get one from `list_niches`."),
656
- confirm: z12.boolean().optional().describe(
741
+ import { z as z13 } from "zod";
742
+ var inputSchema14 = {
743
+ nicheId: z13.string().min(1).describe("The niche to delete. Get one from `list_niches`."),
744
+ confirm: z13.boolean().optional().describe(
657
745
  "Must be true to proceed \u2014 deleting a niche destroys its keywords, competitors and dive history permanently. Confirm with the user first."
658
746
  )
659
747
  };
@@ -661,7 +749,7 @@ var deleteNicheTool = {
661
749
  name: "delete_niche",
662
750
  title: "Delete a Niche",
663
751
  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).",
664
- inputSchema: inputSchema12,
752
+ inputSchema: inputSchema14,
665
753
  annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
666
754
  handler: async (args, ctx) => {
667
755
  const pending = requireConfirmation(
@@ -679,15 +767,15 @@ var deleteNicheTool = {
679
767
  };
680
768
 
681
769
  // src/tools/pause-rank-radar.ts
682
- import { z as z13 } from "zod";
683
- var inputSchema13 = {
684
- rankRadarId: z13.string().uuid().describe("The Rank Radar UUID to pause (from `list_rank_radars`).")
770
+ import { z as z14 } from "zod";
771
+ var inputSchema15 = {
772
+ rankRadarId: z14.string().uuid().describe("The Rank Radar UUID to pause (from `list_rank_radars`).")
685
773
  };
686
774
  var pauseRankRadarTool = {
687
775
  name: "pause_rank_radar",
688
776
  title: "Pause a Rank Radar",
689
777
  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.",
690
- inputSchema: inputSchema13,
778
+ inputSchema: inputSchema15,
691
779
  annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
692
780
  handler: async (args, ctx) => {
693
781
  await httpPost(
@@ -700,15 +788,15 @@ var pauseRankRadarTool = {
700
788
  };
701
789
 
702
790
  // src/tools/resume-rank-radar.ts
703
- import { z as z14 } from "zod";
704
- var inputSchema14 = {
705
- rankRadarId: z14.string().uuid().describe("The Rank Radar UUID to resume (from `list_rank_radars`).")
791
+ import { z as z15 } from "zod";
792
+ var inputSchema16 = {
793
+ rankRadarId: z15.string().uuid().describe("The Rank Radar UUID to resume (from `list_rank_radars`).")
706
794
  };
707
795
  var resumeRankRadarTool = {
708
796
  name: "resume_rank_radar",
709
797
  title: "Resume a Paused Rank Radar",
710
798
  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.",
711
- inputSchema: inputSchema14,
799
+ inputSchema: inputSchema16,
712
800
  annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
713
801
  handler: async (args, ctx) => {
714
802
  await httpPost(
@@ -721,10 +809,10 @@ var resumeRankRadarTool = {
721
809
  };
722
810
 
723
811
  // src/tools/delete-rank-radar.ts
724
- import { z as z15 } from "zod";
725
- var inputSchema15 = {
726
- rankRadarId: z15.string().uuid().describe("The Rank Radar UUID to delete (from `list_rank_radars`)."),
727
- confirm: z15.boolean().optional().describe(
812
+ import { z as z16 } from "zod";
813
+ var inputSchema17 = {
814
+ rankRadarId: z16.string().uuid().describe("The Rank Radar UUID to delete (from `list_rank_radars`)."),
815
+ confirm: z16.boolean().optional().describe(
728
816
  "Must be true to proceed \u2014 deleting a Rank Radar destroys its ranking history permanently. Confirm with the user first."
729
817
  )
730
818
  };
@@ -732,7 +820,7 @@ var deleteRankRadarTool = {
732
820
  name: "delete_rank_radar",
733
821
  title: "Delete a Rank Radar",
734
822
  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`.",
735
- inputSchema: inputSchema15,
823
+ inputSchema: inputSchema17,
736
824
  annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
737
825
  handler: async (args, ctx) => {
738
826
  const pending = requireConfirmation(
@@ -750,10 +838,10 @@ var deleteRankRadarTool = {
750
838
  };
751
839
 
752
840
  // src/tools/add-rank-radar-search-terms.ts
753
- import { z as z16 } from "zod";
754
- var inputSchema16 = {
755
- rankRadarId: z16.string().uuid().describe("The Rank Radar UUID to add the search terms to (from `list_rank_radars`)."),
756
- searchTerms: z16.array(z16.string().min(1)).min(1).max(ADD_SEARCH_TERMS_MAX).describe(
841
+ import { z as z17 } from "zod";
842
+ var inputSchema18 = {
843
+ rankRadarId: z17.string().uuid().describe("The Rank Radar UUID to add the search terms to (from `list_rank_radars`)."),
844
+ searchTerms: z17.array(z17.string().min(1)).min(1).max(ADD_SEARCH_TERMS_MAX).describe(
757
845
  `The keywords to start tracking, 1\u2013${ADD_SEARCH_TERMS_MAX} per call. Must be unique. Amazon-ignored special characters (@, #, _, parentheses, \u2026) are rejected.`
758
846
  )
759
847
  };
@@ -761,7 +849,7 @@ var addRankRadarSearchTermsTool = {
761
849
  name: "add_rank_radar_search_terms",
762
850
  title: "Add Search Terms to a Rank Radar",
763
851
  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.",
764
- inputSchema: inputSchema16,
852
+ inputSchema: inputSchema18,
765
853
  annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
766
854
  handler: async (args, ctx) => {
767
855
  return await httpPost(
@@ -773,10 +861,10 @@ var addRankRadarSearchTermsTool = {
773
861
  };
774
862
 
775
863
  // src/tools/pause-rank-radar-search-terms.ts
776
- import { z as z17 } from "zod";
777
- var inputSchema17 = {
778
- rankRadarId: z17.string().uuid().describe("The Rank Radar UUID the search terms belong to."),
779
- rankRadarKeywordIds: z17.array(z17.string().uuid()).min(1).describe(
864
+ import { z as z18 } from "zod";
865
+ var inputSchema19 = {
866
+ rankRadarId: z18.string().uuid().describe("The Rank Radar UUID the search terms belong to."),
867
+ rankRadarKeywordIds: z18.array(z18.string().uuid()).min(1).describe(
780
868
  "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."
781
869
  )
782
870
  };
@@ -784,7 +872,7 @@ var pauseRankRadarSearchTermsTool = {
784
872
  name: "pause_rank_radar_search_terms",
785
873
  title: "Pause Search Terms of a Rank Radar",
786
874
  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.",
787
- inputSchema: inputSchema17,
875
+ inputSchema: inputSchema19,
788
876
  annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
789
877
  handler: async (args, ctx) => {
790
878
  await httpPost(
@@ -801,10 +889,10 @@ var pauseRankRadarSearchTermsTool = {
801
889
  };
802
890
 
803
891
  // src/tools/resume-rank-radar-search-terms.ts
804
- import { z as z18 } from "zod";
805
- var inputSchema18 = {
806
- rankRadarId: z18.string().uuid().describe("The Rank Radar UUID the search terms belong to."),
807
- rankRadarKeywordIds: z18.array(z18.string().uuid()).min(1).describe(
892
+ import { z as z19 } from "zod";
893
+ var inputSchema20 = {
894
+ rankRadarId: z19.string().uuid().describe("The Rank Radar UUID the search terms belong to."),
895
+ rankRadarKeywordIds: z19.array(z19.string().uuid()).min(1).describe(
808
896
  "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."
809
897
  )
810
898
  };
@@ -812,7 +900,7 @@ var resumeRankRadarSearchTermsTool = {
812
900
  name: "resume_rank_radar_search_terms",
813
901
  title: "Resume Paused Search Terms of a Rank Radar",
814
902
  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.",
815
- inputSchema: inputSchema18,
903
+ inputSchema: inputSchema20,
816
904
  annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
817
905
  handler: async (args, ctx) => {
818
906
  await httpPost(
@@ -829,16 +917,16 @@ var resumeRankRadarSearchTermsTool = {
829
917
  };
830
918
 
831
919
  // src/tools/generate-listing-copy.ts
832
- import { z as z19 } from "zod";
833
- var inputSchema19 = {
834
- nicheId: z19.string().min(1).describe("The niche whose keyword data the copy is written from. Get one from `list_niches`."),
835
- strategy: z19.enum(LISTING_COPY_STRATEGIES).describe(
920
+ import { z as z20 } from "zod";
921
+ var inputSchema21 = {
922
+ nicheId: z20.string().min(1).describe("The niche whose keyword data the copy is written from. Get one from `list_niches`."),
923
+ strategy: z20.enum(LISTING_COPY_STRATEGIES).describe(
836
924
  "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."
837
925
  ),
838
- currentListing: z19.record(z19.union([z19.string(), z19.array(z19.string())])).describe(
926
+ currentListing: z20.record(z20.union([z20.string(), z20.array(z20.string())])).describe(
839
927
  '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.'
840
928
  ),
841
- confirm: z19.boolean().optional().describe(
929
+ confirm: z20.boolean().optional().describe(
842
930
  "Must be true to proceed \u2014 each generation spends one AI Copywriter prompt from the quota. Confirm the cost with the user first."
843
931
  )
844
932
  };
@@ -846,7 +934,7 @@ var generateListingCopyTool = {
846
934
  name: "generate_listing_copy",
847
935
  title: "Generate Listing Copy for a Niche",
848
936
  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`.",
849
- inputSchema: inputSchema19,
937
+ inputSchema: inputSchema21,
850
938
  annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
851
939
  handler: async (args, ctx) => {
852
940
  const pending = requireConfirmation(
@@ -864,16 +952,16 @@ var generateListingCopyTool = {
864
952
  };
865
953
 
866
954
  // src/tools/get-listing-copy-generation-status.ts
867
- import { z as z20 } from "zod";
868
- var inputSchema20 = {
869
- nicheId: z20.string().min(1).describe("The niche the generation was started for."),
870
- generationId: z20.string().min(1).describe("The `generationId` returned by `generate_listing_copy`.")
955
+ import { z as z21 } from "zod";
956
+ var inputSchema22 = {
957
+ nicheId: z21.string().min(1).describe("The niche the generation was started for."),
958
+ generationId: z21.string().min(1).describe("The `generationId` returned by `generate_listing_copy`.")
871
959
  };
872
960
  var getListingCopyGenerationStatusTool = {
873
961
  name: "get_listing_copy_generation_status",
874
962
  title: "Get Listing Copy Generation Status",
875
963
  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`.",
876
- inputSchema: inputSchema20,
964
+ inputSchema: inputSchema22,
877
965
  annotations: { readOnlyHint: true },
878
966
  handler: async (args, ctx) => {
879
967
  return await httpGet(
@@ -884,16 +972,16 @@ var getListingCopyGenerationStatusTool = {
884
972
  };
885
973
 
886
974
  // src/tools/list-seller-profiles.ts
887
- import { z as z21 } from "zod";
888
- var inputSchema21 = {
889
- currentPage: z21.number().int().min(1).optional().describe("Page number, 1-indexed. Defaults to 1."),
890
- pageSize: z21.number().int().min(1).max(50).optional().describe("Items per page (max 50). Defaults to 20.")
975
+ import { z as z22 } from "zod";
976
+ var inputSchema23 = {
977
+ currentPage: z22.number().int().min(1).optional().describe("Page number, 1-indexed. Defaults to 1."),
978
+ pageSize: z22.number().int().min(1).max(50).optional().describe("Items per page (max 50). Defaults to 20.")
891
979
  };
892
980
  var listSellerProfilesTool = {
893
981
  name: "list_seller_profiles",
894
982
  title: "List Connected Amazon Seller Profiles",
895
983
  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).',
896
- inputSchema: inputSchema21,
984
+ inputSchema: inputSchema23,
897
985
  annotations: { readOnlyHint: true },
898
986
  handler: async (args, ctx) => {
899
987
  return await httpGet(
@@ -908,15 +996,15 @@ var listSellerProfilesTool = {
908
996
  };
909
997
 
910
998
  // src/tools/get-seller-catalog.ts
911
- import { z as z23 } from "zod";
999
+ import { z as z24 } from "zod";
912
1000
 
913
1001
  // src/tools/seller-scoped.ts
914
- import { z as z22 } from "zod";
1002
+ import { z as z23 } from "zod";
915
1003
  var sellerScopedInputSchema = {
916
- sellerId: z22.string().min(1).describe(
1004
+ sellerId: z23.string().min(1).describe(
917
1005
  "Amazon seller account ID. Get it from `list_seller_profiles`, or find it on the Connections page at https://2.datadive.tools."
918
1006
  ),
919
- marketplace: z22.enum(SUPPORTED_MARKETPLACES).describe('Amazon marketplace code, e.g. "com" for amazon.com or "co.uk" for amazon.co.uk.')
1007
+ marketplace: z23.enum(SUPPORTED_MARKETPLACES).describe('Amazon marketplace code, e.g. "com" for amazon.com or "co.uk" for amazon.co.uk.')
920
1008
  };
921
1009
  function sellerMarketplacePath(sellerId, marketplace, suffix) {
922
1010
  return `/v1/seller_profiles/${encodeURIComponent(sellerId)}/marketplaces/${encodeURIComponent(
@@ -925,19 +1013,19 @@ function sellerMarketplacePath(sellerId, marketplace, suffix) {
925
1013
  }
926
1014
 
927
1015
  // src/tools/get-seller-catalog.ts
928
- var inputSchema22 = {
1016
+ var inputSchema24 = {
929
1017
  ...sellerScopedInputSchema,
930
- search: z23.string().min(1).optional().describe("Case-insensitive partial match on product title or brand."),
931
- brand: z23.string().min(1).optional().describe("Case-insensitive partial match on brand name."),
932
- status: z23.enum(CATALOG_STATUSES).optional().describe('Listing status filter. "Active" (default) or "all".'),
933
- currentPage: z23.number().int().min(1).optional().describe("Page number, 1-indexed. Defaults to 1."),
934
- pageSize: z23.number().int().min(1).max(50).optional().describe("Items per page (max 50). Defaults to 20.")
1018
+ search: z24.string().min(1).optional().describe("Case-insensitive partial match on product title or brand."),
1019
+ brand: z24.string().min(1).optional().describe("Case-insensitive partial match on brand name."),
1020
+ status: z24.enum(CATALOG_STATUSES).optional().describe('Listing status filter. "Active" (default) or "all".'),
1021
+ currentPage: z24.number().int().min(1).optional().describe("Page number, 1-indexed. Defaults to 1."),
1022
+ pageSize: z24.number().int().min(1).max(50).optional().describe("Items per page (max 50). Defaults to 20.")
935
1023
  };
936
1024
  var getSellerCatalogTool = {
937
1025
  name: "get_seller_catalog",
938
1026
  title: "List Catalog ASINs for a Seller",
939
1027
  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).",
940
- inputSchema: inputSchema22,
1028
+ inputSchema: inputSchema24,
941
1029
  annotations: { readOnlyHint: true },
942
1030
  handler: async (args, ctx) => {
943
1031
  return await httpGet(
@@ -955,28 +1043,28 @@ var getSellerCatalogTool = {
955
1043
  };
956
1044
 
957
1045
  // src/tools/get-seller-listing-changes.ts
958
- import { z as z24 } from "zod";
959
- var inputSchema23 = {
1046
+ import { z as z25 } from "zod";
1047
+ var inputSchema25 = {
960
1048
  ...sellerScopedInputSchema,
961
- types: z24.array(z24.enum(LISTING_CHANGE_TYPES)).min(1).optional().describe('Filter to specific change types. Omit for all. Any of "Price", "Content", "Image".'),
962
- asin: z24.string().min(1).optional().describe("Filter to an exact ASIN or parent ASIN."),
963
- brand: z24.string().min(1).optional().describe("Case-insensitive partial match on brand name."),
964
- search: z24.string().min(1).optional().describe("Case-insensitive partial match on product title or brand."),
965
- startDate: z24.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."),
966
- endDate: z24.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."),
967
- sortBy: z24.enum(LISTING_CHANGE_SORT_BY).optional().describe('Sort field. "date" (default) or "type".'),
968
- sortOrder: z24.enum(SORT_ORDER).optional().describe('Sort direction. "DESC" (default) or "ASC".'),
969
- includeCorrelations: z24.boolean().optional().describe(
1049
+ types: z25.array(z25.enum(LISTING_CHANGE_TYPES)).min(1).optional().describe('Filter to specific change types. Omit for all. Any of "Price", "Content", "Image".'),
1050
+ asin: z25.string().min(1).optional().describe("Filter to an exact ASIN or parent ASIN."),
1051
+ brand: z25.string().min(1).optional().describe("Case-insensitive partial match on brand name."),
1052
+ search: z25.string().min(1).optional().describe("Case-insensitive partial match on product title or brand."),
1053
+ startDate: z25.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."),
1054
+ endDate: z25.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."),
1055
+ sortBy: z25.enum(LISTING_CHANGE_SORT_BY).optional().describe('Sort field. "date" (default) or "type".'),
1056
+ sortOrder: z25.enum(SORT_ORDER).optional().describe('Sort direction. "DESC" (default) or "ASC".'),
1057
+ includeCorrelations: z25.boolean().optional().describe(
970
1058
  "When true, include the ranking/conversion `correlation` object per change (CVR and search-term movement before vs after). Defaults to false."
971
1059
  ),
972
- currentPage: z24.number().int().min(1).optional().describe("Page number, 1-indexed. Defaults to 1."),
973
- pageSize: z24.number().int().min(1).max(50).optional().describe("Items per page (max 50). Defaults to 20.")
1060
+ currentPage: z25.number().int().min(1).optional().describe("Page number, 1-indexed. Defaults to 1."),
1061
+ pageSize: z25.number().int().min(1).max(50).optional().describe("Items per page (max 50). Defaults to 20.")
974
1062
  };
975
1063
  var getSellerListingChangesTool = {
976
1064
  name: "get_seller_listing_changes",
977
1065
  title: "List Listing Changes for a Seller",
978
1066
  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).",
979
- inputSchema: inputSchema23,
1067
+ inputSchema: inputSchema25,
980
1068
  annotations: { readOnlyHint: true },
981
1069
  handler: async (args, ctx) => {
982
1070
  return await httpGet(
@@ -1000,14 +1088,78 @@ var getSellerListingChangesTool = {
1000
1088
  }
1001
1089
  };
1002
1090
 
1091
+ // src/tools/list-ppc-campaigns.ts
1092
+ import { z as z26 } from "zod";
1093
+ var PPC_CAMPAIGNS_MAX_WINDOW_DAYS = 90;
1094
+ var PPC_CAMPAIGNS_MAX_PAGE_SIZE = 50;
1095
+ var inputSchema26 = {
1096
+ ...sellerScopedInputSchema,
1097
+ startDate: z26.string().regex(
1098
+ ISO_8601,
1099
+ "startDate must be an ISO-8601 date or timestamp (e.g. 2026-05-01 or 2026-05-01T00:00:00Z)"
1100
+ ).optional().describe("Start of the reporting window, ISO-8601. Defaults to 30 days before endDate."),
1101
+ endDate: z26.string().regex(
1102
+ ISO_8601,
1103
+ "endDate must be an ISO-8601 date or timestamp (e.g. 2026-05-31 or 2026-05-31T23:59:59Z)"
1104
+ ).optional().describe(
1105
+ `End of the reporting window, ISO-8601. Defaults to now. At most ${PPC_CAMPAIGNS_MAX_WINDOW_DAYS} days after startDate.`
1106
+ ),
1107
+ asin: z26.string().min(1).optional().describe("Only campaigns advertising this ASIN. Cannot be combined with parentAsin."),
1108
+ parentAsin: z26.string().min(1).optional().describe("Only campaigns advertising any ASIN of this variation family. Cannot be combined with asin."),
1109
+ search: z26.string().min(3).max(200).optional().describe("Partial match on campaign name or advertised ASIN. At least 3 characters."),
1110
+ includePlacements: z26.boolean().optional().describe(
1111
+ "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."
1112
+ ),
1113
+ sortBy: z26.enum([...PPC_CAMPAIGN_BASE_SORT_BY, ...PPC_CAMPAIGN_PLACEMENT_SORT_BY]).optional().describe(
1114
+ `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.`
1115
+ ),
1116
+ sortOrder: z26.enum(SORT_ORDER).optional().describe('Sort direction. "ASC" (default) or "DESC".'),
1117
+ state: z26.enum(PPC_CAMPAIGN_STATES).optional().describe("Only ENABLED or only PAUSED campaigns. Omit for both."),
1118
+ currentPage: z26.number().int().min(1).optional().describe("Page number, 1-indexed. Defaults to 1."),
1119
+ pageSize: z26.number().int().min(1).max(PPC_CAMPAIGNS_MAX_PAGE_SIZE).optional().describe(`Campaigns per page (max ${PPC_CAMPAIGNS_MAX_PAGE_SIZE}). Defaults to 20.`)
1120
+ };
1121
+ var listPpcCampaignsTool = {
1122
+ name: "list_ppc_campaigns",
1123
+ title: "List PPC Campaigns for a Seller",
1124
+ 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\`.`,
1125
+ inputSchema: inputSchema26,
1126
+ annotations: { readOnlyHint: true },
1127
+ handler: async (args, ctx) => {
1128
+ if (args.asin && args.parentAsin) {
1129
+ throw new ApiError(
1130
+ "bad_request",
1131
+ 400,
1132
+ "Pass either `asin` (one product) or `parentAsin` (its whole variation family), not both."
1133
+ );
1134
+ }
1135
+ return await httpGet(
1136
+ { config: ctx.config, toolName: "list_ppc_campaigns" },
1137
+ sellerMarketplacePath(args.sellerId, args.marketplace, "ppc/campaigns"),
1138
+ {
1139
+ startDate: args.startDate,
1140
+ endDate: args.endDate,
1141
+ asin: args.asin,
1142
+ parentAsin: args.parentAsin,
1143
+ search: args.search,
1144
+ includePlacements: args.includePlacements,
1145
+ sortBy: args.sortBy,
1146
+ sortOrder: args.sortOrder,
1147
+ state: args.state,
1148
+ currentPage: args.currentPage,
1149
+ pageSize: args.pageSize
1150
+ }
1151
+ );
1152
+ }
1153
+ };
1154
+
1003
1155
  // src/tools/get-asin-inventory-distribution.ts
1004
- import { z as z25 } from "zod";
1005
- var inputSchema24 = {
1006
- sellerId: z25.string().min(1).describe(
1156
+ import { z as z27 } from "zod";
1157
+ var inputSchema27 = {
1158
+ sellerId: z27.string().min(1).describe(
1007
1159
  "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."
1008
1160
  ),
1009
- marketplace: z25.enum(SUPPORTED_MARKETPLACES).describe('Amazon marketplace code, e.g. "com" for amazon.com or "co.uk" for amazon.co.uk.'),
1010
- asin: z25.string().regex(/^[A-Z0-9]{10}$/, "ASIN must be 10 uppercase alphanumeric characters (e.g. B08XYZ1234).").describe(
1161
+ marketplace: z27.enum(SUPPORTED_MARKETPLACES).describe('Amazon marketplace code, e.g. "com" for amazon.com or "co.uk" for amazon.co.uk.'),
1162
+ asin: z27.string().regex(/^[A-Z0-9]{10}$/, "ASIN must be 10 uppercase alphanumeric characters (e.g. B08XYZ1234).").describe(
1011
1163
  "Amazon Standard Identification Number (10 uppercase alphanumeric characters, e.g. B08XYZ1234)."
1012
1164
  )
1013
1165
  };
@@ -1015,7 +1167,7 @@ var getAsinInventoryDistributionTool = {
1015
1167
  name: "get_asin_inventory_distribution",
1016
1168
  title: "Get Per-Fulfillment-Center Inventory Distribution for an ASIN",
1017
1169
  description: "Use this when the user asks about inventory levels, stock by fulfillment center, or where their units are sitting across Amazon's fulfillment network for a specific ASIN. Requires the Amazon sellerId (from `list_seller_profiles` or the user's DataDive Connections page at https://2.datadive.tools), the marketplace code, and the ASIN. Returns totalSellableUnits and a per-FC `distribution` array (fc, state, availableStock, availableStockPercentage). `lastUpdatedAt` may be null when no successful inventory ingestion has occurred in the last 30 days.",
1018
- inputSchema: inputSchema24,
1170
+ inputSchema: inputSchema27,
1019
1171
  annotations: { readOnlyHint: true },
1020
1172
  handler: async (args, ctx) => {
1021
1173
  return await httpGet(
@@ -1028,18 +1180,18 @@ var getAsinInventoryDistributionTool = {
1028
1180
  };
1029
1181
 
1030
1182
  // src/tools/alert-query.ts
1031
- import { z as z26 } from "zod";
1183
+ import { z as z28 } from "zod";
1032
1184
  var alertsQueryInputSchema = {
1033
- sellerId: z26.string().min(1).max(50).optional().describe('Filter to a single connected Amazon seller account ID, e.g. "A1B2C3D4E5".'),
1034
- marketplace: z26.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.'),
1035
- status: z26.enum(ALERT_STATUSES).optional().describe(
1185
+ sellerId: z28.string().min(1).max(50).optional().describe('Filter to a single connected Amazon seller account ID, e.g. "A1B2C3D4E5".'),
1186
+ marketplace: z28.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.'),
1187
+ status: z28.enum(ALERT_STATUSES).optional().describe(
1036
1188
  "Lifecycle filter. `active` (default) returns unresolved alerts, `resolved` returns resolved alerts, `all` returns both. Dismissed alerts are never returned."
1037
1189
  ),
1038
- updatedSince: z26.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(
1190
+ updatedSince: z28.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(
1039
1191
  "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."
1040
1192
  ),
1041
- currentPage: z26.number().int().min(1).optional().describe("Page number, 1-indexed. Defaults to 1."),
1042
- pageSize: z26.number().int().min(1).max(50).optional().describe("Items per page (max 50). Defaults to 20.")
1193
+ currentPage: z28.number().int().min(1).optional().describe("Page number, 1-indexed. Defaults to 1."),
1194
+ pageSize: z28.number().int().min(1).max(50).optional().describe("Items per page (max 50). Defaults to 20.")
1043
1195
  };
1044
1196
 
1045
1197
  // src/tools/list-indexing-issue-alerts.ts
@@ -1089,12 +1241,12 @@ var listBlindSpendAlertsTool = {
1089
1241
  };
1090
1242
 
1091
1243
  // src/tools/get-quota.ts
1092
- var inputSchema25 = {};
1244
+ var inputSchema28 = {};
1093
1245
  var getQuotaTool = {
1094
1246
  name: "get_quota",
1095
1247
  title: "Get Quota Usage",
1096
1248
  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.",
1097
- inputSchema: inputSchema25,
1249
+ inputSchema: inputSchema28,
1098
1250
  annotations: { readOnlyHint: true },
1099
1251
  handler: async (_args, ctx) => {
1100
1252
  return await httpGet({ config: ctx.config, toolName: "get_quota" }, "/v1/quota");
@@ -1102,20 +1254,20 @@ var getQuotaTool = {
1102
1254
  };
1103
1255
 
1104
1256
  // src/tools/list-usage.ts
1105
- import { z as z27 } from "zod";
1106
- var inputSchema26 = {
1107
- type: z27.enum(BILLABLE_FEATURE_TYPES).optional().describe("Filter to a single billable feature type. Omit to return all types."),
1108
- search: z27.string().min(1).optional().describe("Case-insensitive partial match on the user's name or email."),
1109
- startDate: z27.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."),
1110
- endDate: z27.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."),
1111
- currentPage: z27.number().int().min(1).optional().describe("Page number, 1-indexed. Defaults to 1."),
1112
- pageSize: z27.number().int().min(1).max(200).optional().describe("Items per page (max 200). Defaults to 50.")
1257
+ import { z as z29 } from "zod";
1258
+ var inputSchema29 = {
1259
+ type: z29.enum(BILLABLE_FEATURE_TYPES).optional().describe("Filter to a single billable feature type. Omit to return all types."),
1260
+ search: z29.string().min(1).optional().describe("Case-insensitive partial match on the user's name or email."),
1261
+ startDate: z29.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."),
1262
+ endDate: z29.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."),
1263
+ currentPage: z29.number().int().min(1).optional().describe("Page number, 1-indexed. Defaults to 1."),
1264
+ pageSize: z29.number().int().min(1).max(200).optional().describe("Items per page (max 200). Defaults to 50.")
1113
1265
  };
1114
1266
  var listUsageTool = {
1115
1267
  name: "list_usage",
1116
1268
  title: "List Billable Usage Logs",
1117
1269
  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).",
1118
- inputSchema: inputSchema26,
1270
+ inputSchema: inputSchema29,
1119
1271
  annotations: { readOnlyHint: true },
1120
1272
  handler: async (args, ctx) => {
1121
1273
  return await httpGet(
@@ -1142,6 +1294,8 @@ var allTools = [
1142
1294
  getRankingJuiceTool,
1143
1295
  listRankRadarsTool,
1144
1296
  getRankRadarDataTool,
1297
+ getRankRadarSqpDataTool,
1298
+ getRankRadarPpcDataTool,
1145
1299
  createRankRadarTool,
1146
1300
  createNicheDiveTool,
1147
1301
  rediveNicheTool,
@@ -1158,6 +1312,7 @@ var allTools = [
1158
1312
  listSellerProfilesTool,
1159
1313
  getSellerCatalogTool,
1160
1314
  getSellerListingChangesTool,
1315
+ listPpcCampaignsTool,
1161
1316
  getAsinInventoryDistributionTool,
1162
1317
  listIndexingIssueAlertsTool,
1163
1318
  listBlindSpendAlertsTool,
@@ -1165,15 +1320,80 @@ var allTools = [
1165
1320
  listUsageTool
1166
1321
  ];
1167
1322
 
1323
+ // src/whats-new.ts
1324
+ var WHATS_NEW = [
1325
+ {
1326
+ version: "0.15.0",
1327
+ released: "2026-09-28",
1328
+ items: [
1329
+ "Search Query Performance for Rank Radar keywords (`get_rank_radar_sqp_data`): how shoppers search, click, add to cart and buy, for the whole market and for your product.",
1330
+ "PPC data for Rank Radar keywords (`get_rank_radar_ppc_data`): sponsored rank, spend, sales, ACOS and CPC per keyword, optionally broken down by campaign.",
1331
+ "Your Sponsored Products campaigns (`list_ppc_campaigns`): totals and placement performance per campaign, sortable by any metric, e.g. the top spenders first."
1332
+ ]
1333
+ },
1334
+ {
1335
+ version: "0.14.0",
1336
+ released: "2026-09-25",
1337
+ items: [
1338
+ "Rank Radar keywords are now read page by page (`get_rank_radar_data`, up to 100 keywords per page), so large Rank Radars load reliably."
1339
+ ]
1340
+ },
1341
+ {
1342
+ version: "0.13.0",
1343
+ released: "2026-09-09",
1344
+ items: [
1345
+ "Before creating a Rank Radar, you are warned when the same product family is already tracked by another of your Rank Radars."
1346
+ ]
1347
+ },
1348
+ {
1349
+ version: "0.12.0",
1350
+ released: "2026-09-07",
1351
+ items: ["Search and sort your niches by name, hero keyword or ASIN (`list_niches`)."]
1352
+ }
1353
+ ];
1354
+ var INSTRUCTIONS_RELEASES = 2;
1355
+ function formatEntries(entries) {
1356
+ return entries.map((e) => `${e.released} (v${e.version}):
1357
+ ${e.items.map((i) => `- ${i}`).join("\n")}`).join("\n\n");
1358
+ }
1359
+ var SERVER_INSTRUCTIONS = `DataDive tools for Amazon sellers: niche research (niches, keywords, roots, competitors, Ranking Juice), Rank Radar keyword rank tracking with SQP and PPC data, the seller's catalog, listing changes, inventory, PPC campaigns and alerts. Start with a discovery tool: \`list_niches\`, \`list_rank_radars\` or \`list_seller_profiles\`. Tools that spend tokens ask for confirmation first.
1360
+
1361
+ Recently added:
1362
+ ${formatEntries(WHATS_NEW.slice(0, INSTRUCTIONS_RELEASES))}
1363
+
1364
+ When one of these additions fits what the user is doing, mention it once. The user can see the full list with the \`whats_new\` prompt.`;
1365
+ var WHATS_NEW_PROMPT = {
1366
+ name: "whats_new",
1367
+ title: "What's new in DataDive",
1368
+ description: "Recent additions to the DataDive tools, newest first."
1369
+ };
1370
+ function whatsNewPromptText() {
1371
+ return `Here is what is new in the DataDive tools, newest first:
1372
+
1373
+ ${formatEntries(WHATS_NEW)}
1374
+
1375
+ Summarize this for me in a few short lines, and for each addition give one example question I could ask you.`;
1376
+ }
1377
+
1168
1378
  // src/server.ts
1169
1379
  function requiredScope(tool) {
1170
1380
  return tool.annotations?.readOnlyHint === true ? SCOPE_READ : SCOPE_WRITE;
1171
1381
  }
1172
1382
  function buildServer(config) {
1173
- const server = new McpServer({
1174
- name: "datadive",
1175
- version: PKG_VERSION
1176
- });
1383
+ const server = new McpServer(
1384
+ {
1385
+ name: "datadive",
1386
+ version: PKG_VERSION
1387
+ },
1388
+ { instructions: SERVER_INSTRUCTIONS }
1389
+ );
1390
+ server.registerPrompt(
1391
+ WHATS_NEW_PROMPT.name,
1392
+ { title: WHATS_NEW_PROMPT.title, description: WHATS_NEW_PROMPT.description },
1393
+ () => ({
1394
+ messages: [{ role: "user", content: { type: "text", text: whatsNewPromptText() } }]
1395
+ })
1396
+ );
1177
1397
  for (const tool of allTools) {
1178
1398
  server.registerTool(
1179
1399
  tool.name,
@@ -1233,9 +1453,13 @@ export {
1233
1453
  PKG_VERSION,
1234
1454
  SCOPE_READ,
1235
1455
  SCOPE_WRITE,
1456
+ SERVER_INSTRUCTIONS,
1457
+ WHATS_NEW,
1458
+ WHATS_NEW_PROMPT,
1236
1459
  allTools,
1237
1460
  buildServer,
1238
1461
  loadConfig,
1239
- requiredScope
1462
+ requiredScope,
1463
+ whatsNewPromptText
1240
1464
  };
1241
1465
  //# sourceMappingURL=lib.js.map