@datadive-tools/mcp 0.7.0 → 0.9.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 +23 -8
- package/dist/index.js +153 -62
- package/dist/index.js.map +1 -1
- package/dist/lib.d.ts +184 -0
- package/dist/lib.js +909 -0
- package/dist/lib.js.map +1 -0
- package/package.json +11 -3
package/README.md
CHANGED
|
@@ -16,6 +16,8 @@ except as the `x-api-key` header on requests to `api.datadive.tools`.
|
|
|
16
16
|
- "Show me my rank radars."
|
|
17
17
|
- "Plot the organic ranking trend for rank radar Y from 2024-03-01 to 2024-04-01."
|
|
18
18
|
- "Run a niche dive on ASIN B08N5WRWNW in the US marketplace with 5 competitors."
|
|
19
|
+
- "Refresh niche X with today's data, same competitors."
|
|
20
|
+
- "Re-dive niche X with 12 competitors but keep B08N5WRWNW in the set."
|
|
19
21
|
- "Is my dive done yet?"
|
|
20
22
|
- "Start a rank radar tracking 10 keywords for ASIN B08N5WRWNW in niche X."
|
|
21
23
|
- "Which Amazon seller accounts are connected?"
|
|
@@ -103,7 +105,8 @@ niches, plus pagination metadata. If you don't, see Troubleshooting below.
|
|
|
103
105
|
| `list_rank_radars` | Paginated list of rank radars. Filter by `nicheId` or `status`. |
|
|
104
106
|
| `get_rank_radar_data` | Historical keyword rankings for a rank radar within a `startDate`/`endDate` range. |
|
|
105
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`. |
|
|
106
|
-
| `
|
|
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
|
+
| `get_dive_status` | Poll a dive started by `create_niche_dive` or `redive_niche`: `in_progress`, `success` (carries the `nicheId`), or `error`. |
|
|
107
110
|
| `create_rank_radar` | **Spends Search Term tokens.** Starts tracking keyword rankings for an ASIN in a niche. Returns a `rankRadarId`. Requires `confirm: true`. |
|
|
108
111
|
| `list_seller_profiles` | Paginated list of connected Amazon seller accounts. Discovery step — returns the `sellerId` + `marketplace` the seller-scoped tools below (and the alert tools) need. |
|
|
109
112
|
| `get_seller_catalog` | Paginated catalog of a seller's own ASINs. Filter by `search`, `brand`, and `status` (Active by default). |
|
|
@@ -115,17 +118,18 @@ niches, plus pagination metadata. If you don't, see Troubleshooting below.
|
|
|
115
118
|
| `list_usage` | Paginated billable usage logs (token-consumption events). Filter by `type`, `search` (user), and `startDate`/`endDate`. |
|
|
116
119
|
|
|
117
120
|
All data is scoped to the organization that owns the API key. Most tools are
|
|
118
|
-
read-only; the
|
|
119
|
-
|
|
121
|
+
read-only; the three tools below spend tokens (`get_dive_status` only polls a
|
|
122
|
+
dive and is read-only) — see
|
|
120
123
|
[Creating dives & rank radars](#creating-dives--rank-radars).
|
|
121
124
|
|
|
122
125
|
### Creating dives & rank radars
|
|
123
126
|
|
|
124
|
-
`create_niche_dive` and `create_rank_radar` **consume billable
|
|
125
|
-
be undone**, so they require an explicit `confirm: true`
|
|
126
|
-
should confirm the cost with you before passing it. The
|
|
127
|
-
`numberOfCompetitors` (dives
|
|
128
|
-
balance any time with
|
|
127
|
+
`create_niche_dive`, `redive_niche` and `create_rank_radar` **consume billable
|
|
128
|
+
tokens and cannot be undone**, so they require an explicit `confirm: true`
|
|
129
|
+
argument. The assistant should confirm the cost with you before passing it. The
|
|
130
|
+
amount scales with `numberOfCompetitors` (dives and re-dives) /
|
|
131
|
+
`numberOfKeywords` (rank radars). Check remaining balance any time with
|
|
132
|
+
`get_quota`.
|
|
129
133
|
|
|
130
134
|
To skip the per-call confirmation (e.g. in an automated setup), set
|
|
131
135
|
`DATADIVE_AUTO_CONFIRM_WRITES=true` in your client config — then these tools run
|
|
@@ -136,6 +140,17 @@ estimated completion time immediately; poll `get_dive_status` with that `diveId`
|
|
|
136
140
|
until it reports `success`, which carries the new `nicheId` you then feed to
|
|
137
141
|
`list_niches`, `get_niche_keywords`, and the other niche tools.
|
|
138
142
|
|
|
143
|
+
`redive_niche` refreshes a niche you already have rather than creating another
|
|
144
|
+
one, and it keeps the same `nicheId` — so rank radars and reports built on that
|
|
145
|
+
niche follow the refreshed data. It takes a `mode`:
|
|
146
|
+
|
|
147
|
+
- `same_competitors` — re-dive the niche's current competitor set. Nothing else
|
|
148
|
+
to supply; use it purely to pull in current sales, price and keyword data.
|
|
149
|
+
- `discover` — search for a fresh competitor set of `numberOfCompetitors` ASINs.
|
|
150
|
+
Steer it with `heroAsin` (the seed to build around; defaults to the niche's
|
|
151
|
+
highest-selling competitor, preferring one of your own ASINs), `lockedAsins`
|
|
152
|
+
(kept no matter what discovery finds) and `excludedAsins` (never selected).
|
|
153
|
+
|
|
139
154
|
> ⚠️ For `create_niche_dive`, `marketplace` uses full Amazon domain suffixes
|
|
140
155
|
> (`com`, `co.uk`, `com.mx`, `co.jp`, …) — e.g. the UK marketplace is `co.uk`,
|
|
141
156
|
> not `uk`.
|
package/dist/index.js
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5
5
|
|
|
6
6
|
// src/config.ts
|
|
7
|
+
var SCOPE_READ = "datadive.read";
|
|
8
|
+
var SCOPE_WRITE = "datadive.write";
|
|
7
9
|
var DEFAULT_BASE_URL = "https://api.datadive.tools";
|
|
8
10
|
var KEY_HELP_URL = "https://2.datadive.tools/api-key";
|
|
9
11
|
function loadConfig(env = process.env) {
|
|
@@ -18,7 +20,7 @@ function loadConfig(env = process.env) {
|
|
|
18
20
|
const autoConfirmWrites = ["1", "true", "yes"].includes(
|
|
19
21
|
(env.DATADIVE_AUTO_CONFIRM_WRITES ?? "").trim().toLowerCase()
|
|
20
22
|
);
|
|
21
|
-
return { apiKey, baseUrl, autoConfirmWrites };
|
|
23
|
+
return { credentials: { kind: "api-key", apiKey }, baseUrl, autoConfirmWrites };
|
|
22
24
|
}
|
|
23
25
|
|
|
24
26
|
// src/server.ts
|
|
@@ -103,7 +105,7 @@ function extractMessage(body) {
|
|
|
103
105
|
}
|
|
104
106
|
|
|
105
107
|
// package.json
|
|
106
|
-
var version = "0.
|
|
108
|
+
var version = "0.9.0";
|
|
107
109
|
|
|
108
110
|
// src/http/client.ts
|
|
109
111
|
var PKG_VERSION = version;
|
|
@@ -142,7 +144,7 @@ async function httpGet(ctx, path, query) {
|
|
|
142
144
|
res = await fetch(url, {
|
|
143
145
|
method: "GET",
|
|
144
146
|
headers: {
|
|
145
|
-
|
|
147
|
+
...authHeaders(ctx.config),
|
|
146
148
|
accept: "application/json",
|
|
147
149
|
"user-agent": userAgent(ctx.toolName)
|
|
148
150
|
}
|
|
@@ -159,7 +161,7 @@ async function httpPost(ctx, path, body) {
|
|
|
159
161
|
res = await fetch(url, {
|
|
160
162
|
method: "POST",
|
|
161
163
|
headers: {
|
|
162
|
-
|
|
164
|
+
...authHeaders(ctx.config),
|
|
163
165
|
accept: "application/json",
|
|
164
166
|
"content-type": "application/json",
|
|
165
167
|
"user-agent": userAgent(ctx.toolName)
|
|
@@ -203,6 +205,10 @@ function buildUrl(baseUrl, path, query) {
|
|
|
203
205
|
function userAgent(toolName) {
|
|
204
206
|
return `datadive-mcp/${PKG_VERSION} tool/${toolName}`;
|
|
205
207
|
}
|
|
208
|
+
function authHeaders(config) {
|
|
209
|
+
const c = config.credentials;
|
|
210
|
+
return c.kind === "bearer" ? { authorization: `Bearer ${c.token}` } : { "x-api-key": c.apiKey };
|
|
211
|
+
}
|
|
206
212
|
function unwrap(body) {
|
|
207
213
|
if (!isObject(body)) return body;
|
|
208
214
|
if (!("data" in body)) return body;
|
|
@@ -225,6 +231,7 @@ var listNichesTool = {
|
|
|
225
231
|
title: "List DataDive Niches",
|
|
226
232
|
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. 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).",
|
|
227
233
|
inputSchema,
|
|
234
|
+
annotations: { readOnlyHint: true },
|
|
228
235
|
handler: async (args, ctx) => {
|
|
229
236
|
return await httpGet(
|
|
230
237
|
{ config: ctx.config, toolName: "list_niches" },
|
|
@@ -244,6 +251,7 @@ var getNicheKeywordsTool = {
|
|
|
244
251
|
title: "Get Master Keyword List for a Niche",
|
|
245
252
|
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 }). Also returns the latestResearchDate.',
|
|
246
253
|
inputSchema: inputSchema2,
|
|
254
|
+
annotations: { readOnlyHint: true },
|
|
247
255
|
handler: async (args, ctx) => {
|
|
248
256
|
return await httpGet(
|
|
249
257
|
{ config: ctx.config, toolName: "get_niche_keywords" },
|
|
@@ -262,6 +270,7 @@ var getNicheRootsTool = {
|
|
|
262
270
|
title: "Get Keyword Roots for a Niche",
|
|
263
271
|
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.",
|
|
264
272
|
inputSchema: inputSchema3,
|
|
273
|
+
annotations: { readOnlyHint: true },
|
|
265
274
|
handler: async (args, ctx) => {
|
|
266
275
|
return await httpGet(
|
|
267
276
|
{ config: ctx.config, toolName: "get_niche_roots" },
|
|
@@ -280,6 +289,7 @@ var getNicheCompetitorsTool = {
|
|
|
280
289
|
title: "Get Competitors and Niche Statistics",
|
|
281
290
|
description: "Use this for competitor ASINs, 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, sales, revenue, ratings, reviews, price, image URL, and ranking data.",
|
|
282
291
|
inputSchema: inputSchema4,
|
|
292
|
+
annotations: { readOnlyHint: true },
|
|
283
293
|
handler: async (args, ctx) => {
|
|
284
294
|
return await httpGet(
|
|
285
295
|
{ config: ctx.config, toolName: "get_niche_competitors" },
|
|
@@ -298,6 +308,7 @@ var getRankingJuiceTool = {
|
|
|
298
308
|
title: "Get Ranking Juices for a Niche",
|
|
299
309
|
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.",
|
|
300
310
|
inputSchema: inputSchema5,
|
|
311
|
+
annotations: { readOnlyHint: true },
|
|
301
312
|
handler: async (args, ctx) => {
|
|
302
313
|
return await httpGet(
|
|
303
314
|
{ config: ctx.config, toolName: "get_ranking_juice" },
|
|
@@ -351,6 +362,7 @@ var listRankRadarsTool = {
|
|
|
351
362
|
title: "List Rank Radars",
|
|
352
363
|
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, asin (with id, krtId, asin), marketplace, keywordCount, title, imageUrl, and summary metrics: top10KW, top10SV, top50KW, top50SV. Supports pagination metadata (currentPage, pageSize, total, lastPage, hasNext, hasPrev).",
|
|
353
364
|
inputSchema: inputSchema6,
|
|
365
|
+
annotations: { readOnlyHint: true },
|
|
354
366
|
handler: async (args, ctx) => {
|
|
355
367
|
return await httpGet(
|
|
356
368
|
{ config: ctx.config, toolName: "list_rank_radars" },
|
|
@@ -379,6 +391,7 @@ var getRankRadarDataTool = {
|
|
|
379
391
|
title: "Get Keyword Rankings for a Rank Radar",
|
|
380
392
|
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`.",
|
|
381
393
|
inputSchema: inputSchema7,
|
|
394
|
+
annotations: { readOnlyHint: true },
|
|
382
395
|
handler: async (args, ctx) => {
|
|
383
396
|
return await httpGet(
|
|
384
397
|
{ config: ctx.config, toolName: "get_rank_radar_data" },
|
|
@@ -476,16 +489,73 @@ var createNicheDiveTool = {
|
|
|
476
489
|
}
|
|
477
490
|
};
|
|
478
491
|
|
|
479
|
-
// src/tools/
|
|
492
|
+
// src/tools/redive-niche.ts
|
|
480
493
|
import { z as z10 } from "zod";
|
|
481
494
|
var inputSchema10 = {
|
|
482
|
-
|
|
495
|
+
nicheId: z10.string().min(1).describe("The niche to re-dive. Get one from `list_niches`."),
|
|
496
|
+
mode: z10.enum(["same_competitors", "discover"]).describe(
|
|
497
|
+
"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."
|
|
498
|
+
),
|
|
499
|
+
numberOfCompetitors: z10.number().int().min(2).optional().describe(
|
|
500
|
+
"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."
|
|
501
|
+
),
|
|
502
|
+
heroAsin: z10.string().min(1).optional().describe(
|
|
503
|
+
"`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."
|
|
504
|
+
),
|
|
505
|
+
lockedAsins: z10.array(z10.string().min(1)).optional().describe(
|
|
506
|
+
"`discover` mode only. ASINs that must stay in the competitor set regardless of what discovery finds. Cannot exceed `numberOfCompetitors`, and cannot overlap `excludedAsins`."
|
|
507
|
+
),
|
|
508
|
+
excludedAsins: z10.array(z10.string().min(1)).optional().describe("`discover` mode only. ASINs discovery must never select."),
|
|
509
|
+
confirm: z10.boolean().optional().describe(
|
|
510
|
+
"Must be true to proceed \u2014 a re-dive spends dive tokens. Confirm the cost with the user first."
|
|
511
|
+
)
|
|
512
|
+
};
|
|
513
|
+
var rediveNicheTool = {
|
|
514
|
+
name: "redive_niche",
|
|
515
|
+
title: "Re-dive an Existing Niche",
|
|
516
|
+
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. 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.",
|
|
517
|
+
inputSchema: inputSchema10,
|
|
518
|
+
// Same reasoning as create_niche_dive: a re-dive is additive rather than destructive, but the
|
|
519
|
+
// irreversible token spend is what clients need to prompt on. The confirm gate enforces it regardless.
|
|
520
|
+
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
521
|
+
handler: async (args, ctx) => {
|
|
522
|
+
if (args.mode === "discover" && args.numberOfCompetitors === void 0) {
|
|
523
|
+
throw new ApiError(
|
|
524
|
+
"bad_request",
|
|
525
|
+
400,
|
|
526
|
+
'numberOfCompetitors is required in `discover` mode \u2014 it is how many ASINs the refreshed dive will contain, and therefore what it costs. Ask the user how many competitors they want (minimum 2), or use mode "same_competitors" to re-dive the niche\'s current set.'
|
|
527
|
+
);
|
|
528
|
+
}
|
|
529
|
+
const pending = requireConfirmation(
|
|
530
|
+
args.confirm,
|
|
531
|
+
ctx,
|
|
532
|
+
args.mode === "discover" ? `Re-diving this niche consumes dive tokens \u2014 one batch per ASIN analyzed (${args.numberOfCompetitors} requested).` : "Re-diving this niche consumes dive tokens \u2014 one batch per competitor currently in the niche. Use `get_niche_competitors` first if the user needs the exact count."
|
|
533
|
+
);
|
|
534
|
+
if (pending) return pending;
|
|
535
|
+
return await httpPost(
|
|
536
|
+
{ config: ctx.config, toolName: "redive_niche" },
|
|
537
|
+
`/v1/niches/${encodeURIComponent(args.nicheId)}/redive`,
|
|
538
|
+
{
|
|
539
|
+
mode: args.mode,
|
|
540
|
+
numberOfCompetitors: args.numberOfCompetitors,
|
|
541
|
+
heroAsin: args.heroAsin,
|
|
542
|
+
lockedAsins: args.lockedAsins,
|
|
543
|
+
excludedAsins: args.excludedAsins
|
|
544
|
+
}
|
|
545
|
+
);
|
|
546
|
+
}
|
|
547
|
+
};
|
|
548
|
+
|
|
549
|
+
// src/tools/get-dive-status.ts
|
|
550
|
+
import { z as z11 } from "zod";
|
|
551
|
+
var inputSchema11 = {
|
|
552
|
+
diveId: z11.string().min(1).describe("The dive identifier returned by `create_niche_dive` or `redive_niche`.")
|
|
483
553
|
};
|
|
484
554
|
var getDiveStatusTool = {
|
|
485
555
|
name: "get_dive_status",
|
|
486
556
|
title: "Get Niche Dive Status",
|
|
487
|
-
description: "Use this to poll a niche dive started with `create_niche_dive` until it finishes. Returns one of three shapes keyed by `status`: `in_progress` (with `estimatedCompletionDate`), `success` (with the
|
|
488
|
-
inputSchema:
|
|
557
|
+
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.",
|
|
558
|
+
inputSchema: inputSchema11,
|
|
489
559
|
annotations: { readOnlyHint: true },
|
|
490
560
|
handler: async (args, ctx) => {
|
|
491
561
|
return await httpGet(
|
|
@@ -496,16 +566,16 @@ var getDiveStatusTool = {
|
|
|
496
566
|
};
|
|
497
567
|
|
|
498
568
|
// src/tools/list-seller-profiles.ts
|
|
499
|
-
import { z as
|
|
500
|
-
var
|
|
501
|
-
currentPage:
|
|
502
|
-
pageSize:
|
|
569
|
+
import { z as z12 } from "zod";
|
|
570
|
+
var inputSchema12 = {
|
|
571
|
+
currentPage: z12.number().int().min(1).optional().describe("Page number, 1-indexed. Defaults to 1."),
|
|
572
|
+
pageSize: z12.number().int().min(1).max(50).optional().describe("Items per page (max 50). Defaults to 20.")
|
|
503
573
|
};
|
|
504
574
|
var listSellerProfilesTool = {
|
|
505
575
|
name: "list_seller_profiles",
|
|
506
576
|
title: "List Connected Amazon Seller Profiles",
|
|
507
577
|
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).',
|
|
508
|
-
inputSchema:
|
|
578
|
+
inputSchema: inputSchema12,
|
|
509
579
|
annotations: { readOnlyHint: true },
|
|
510
580
|
handler: async (args, ctx) => {
|
|
511
581
|
return await httpGet(
|
|
@@ -520,15 +590,15 @@ var listSellerProfilesTool = {
|
|
|
520
590
|
};
|
|
521
591
|
|
|
522
592
|
// src/tools/get-seller-catalog.ts
|
|
523
|
-
import { z as
|
|
593
|
+
import { z as z14 } from "zod";
|
|
524
594
|
|
|
525
595
|
// src/tools/seller-scoped.ts
|
|
526
|
-
import { z as
|
|
596
|
+
import { z as z13 } from "zod";
|
|
527
597
|
var sellerScopedInputSchema = {
|
|
528
|
-
sellerId:
|
|
598
|
+
sellerId: z13.string().min(1).describe(
|
|
529
599
|
"Amazon seller account ID. Get it from `list_seller_profiles`, or find it on the Connections page at https://2.datadive.tools."
|
|
530
600
|
),
|
|
531
|
-
marketplace:
|
|
601
|
+
marketplace: z13.enum(SUPPORTED_MARKETPLACES).describe('Amazon marketplace code, e.g. "com" for amazon.com or "co.uk" for amazon.co.uk.')
|
|
532
602
|
};
|
|
533
603
|
function sellerMarketplacePath(sellerId, marketplace, suffix) {
|
|
534
604
|
return `/v1/seller_profiles/${encodeURIComponent(sellerId)}/marketplaces/${encodeURIComponent(
|
|
@@ -537,19 +607,19 @@ function sellerMarketplacePath(sellerId, marketplace, suffix) {
|
|
|
537
607
|
}
|
|
538
608
|
|
|
539
609
|
// src/tools/get-seller-catalog.ts
|
|
540
|
-
var
|
|
610
|
+
var inputSchema13 = {
|
|
541
611
|
...sellerScopedInputSchema,
|
|
542
|
-
search:
|
|
543
|
-
brand:
|
|
544
|
-
status:
|
|
545
|
-
currentPage:
|
|
546
|
-
pageSize:
|
|
612
|
+
search: z14.string().min(1).optional().describe("Case-insensitive partial match on product title or brand."),
|
|
613
|
+
brand: z14.string().min(1).optional().describe("Case-insensitive partial match on brand name."),
|
|
614
|
+
status: z14.enum(CATALOG_STATUSES).optional().describe('Listing status filter. "Active" (default) or "all".'),
|
|
615
|
+
currentPage: z14.number().int().min(1).optional().describe("Page number, 1-indexed. Defaults to 1."),
|
|
616
|
+
pageSize: z14.number().int().min(1).max(50).optional().describe("Items per page (max 50). Defaults to 20.")
|
|
547
617
|
};
|
|
548
618
|
var getSellerCatalogTool = {
|
|
549
619
|
name: "get_seller_catalog",
|
|
550
620
|
title: "List Catalog ASINs for a Seller",
|
|
551
621
|
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).",
|
|
552
|
-
inputSchema:
|
|
622
|
+
inputSchema: inputSchema13,
|
|
553
623
|
annotations: { readOnlyHint: true },
|
|
554
624
|
handler: async (args, ctx) => {
|
|
555
625
|
return await httpGet(
|
|
@@ -567,28 +637,28 @@ var getSellerCatalogTool = {
|
|
|
567
637
|
};
|
|
568
638
|
|
|
569
639
|
// src/tools/get-seller-listing-changes.ts
|
|
570
|
-
import { z as
|
|
571
|
-
var
|
|
640
|
+
import { z as z15 } from "zod";
|
|
641
|
+
var inputSchema14 = {
|
|
572
642
|
...sellerScopedInputSchema,
|
|
573
|
-
types:
|
|
574
|
-
asin:
|
|
575
|
-
brand:
|
|
576
|
-
search:
|
|
577
|
-
startDate:
|
|
578
|
-
endDate:
|
|
579
|
-
sortBy:
|
|
580
|
-
sortOrder:
|
|
581
|
-
includeCorrelations:
|
|
643
|
+
types: z15.array(z15.enum(LISTING_CHANGE_TYPES)).min(1).optional().describe('Filter to specific change types. Omit for all. Any of "Price", "Content", "Image".'),
|
|
644
|
+
asin: z15.string().min(1).optional().describe("Filter to an exact ASIN or parent ASIN."),
|
|
645
|
+
brand: z15.string().min(1).optional().describe("Case-insensitive partial match on brand name."),
|
|
646
|
+
search: z15.string().min(1).optional().describe("Case-insensitive partial match on product title or brand."),
|
|
647
|
+
startDate: z15.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."),
|
|
648
|
+
endDate: z15.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."),
|
|
649
|
+
sortBy: z15.enum(LISTING_CHANGE_SORT_BY).optional().describe('Sort field. "date" (default) or "type".'),
|
|
650
|
+
sortOrder: z15.enum(SORT_ORDER).optional().describe('Sort direction. "DESC" (default) or "ASC".'),
|
|
651
|
+
includeCorrelations: z15.boolean().optional().describe(
|
|
582
652
|
"When true, include the ranking/conversion `correlation` object per change (CVR and search-term movement before vs after). Defaults to false."
|
|
583
653
|
),
|
|
584
|
-
currentPage:
|
|
585
|
-
pageSize:
|
|
654
|
+
currentPage: z15.number().int().min(1).optional().describe("Page number, 1-indexed. Defaults to 1."),
|
|
655
|
+
pageSize: z15.number().int().min(1).max(50).optional().describe("Items per page (max 50). Defaults to 20.")
|
|
586
656
|
};
|
|
587
657
|
var getSellerListingChangesTool = {
|
|
588
658
|
name: "get_seller_listing_changes",
|
|
589
659
|
title: "List Listing Changes for a Seller",
|
|
590
660
|
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).",
|
|
591
|
-
inputSchema:
|
|
661
|
+
inputSchema: inputSchema14,
|
|
592
662
|
annotations: { readOnlyHint: true },
|
|
593
663
|
handler: async (args, ctx) => {
|
|
594
664
|
return await httpGet(
|
|
@@ -613,13 +683,13 @@ var getSellerListingChangesTool = {
|
|
|
613
683
|
};
|
|
614
684
|
|
|
615
685
|
// src/tools/get-asin-inventory-distribution.ts
|
|
616
|
-
import { z as
|
|
617
|
-
var
|
|
618
|
-
sellerId:
|
|
686
|
+
import { z as z16 } from "zod";
|
|
687
|
+
var inputSchema15 = {
|
|
688
|
+
sellerId: z16.string().min(1).describe(
|
|
619
689
|
"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."
|
|
620
690
|
),
|
|
621
|
-
marketplace:
|
|
622
|
-
asin:
|
|
691
|
+
marketplace: z16.enum(SUPPORTED_MARKETPLACES).describe('Amazon marketplace code, e.g. "com" for amazon.com or "co.uk" for amazon.co.uk.'),
|
|
692
|
+
asin: z16.string().regex(/^[A-Z0-9]{10}$/, "ASIN must be 10 uppercase alphanumeric characters (e.g. B08XYZ1234).").describe(
|
|
623
693
|
"Amazon Standard Identification Number (10 uppercase alphanumeric characters, e.g. B08XYZ1234)."
|
|
624
694
|
)
|
|
625
695
|
};
|
|
@@ -627,7 +697,8 @@ var getAsinInventoryDistributionTool = {
|
|
|
627
697
|
name: "get_asin_inventory_distribution",
|
|
628
698
|
title: "Get Per-Fulfillment-Center Inventory Distribution for an ASIN",
|
|
629
699
|
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.",
|
|
630
|
-
inputSchema:
|
|
700
|
+
inputSchema: inputSchema15,
|
|
701
|
+
annotations: { readOnlyHint: true },
|
|
631
702
|
handler: async (args, ctx) => {
|
|
632
703
|
return await httpGet(
|
|
633
704
|
{ config: ctx.config, toolName: "get_asin_inventory_distribution" },
|
|
@@ -639,18 +710,18 @@ var getAsinInventoryDistributionTool = {
|
|
|
639
710
|
};
|
|
640
711
|
|
|
641
712
|
// src/tools/alert-query.ts
|
|
642
|
-
import { z as
|
|
713
|
+
import { z as z17 } from "zod";
|
|
643
714
|
var alertsQueryInputSchema = {
|
|
644
|
-
sellerId:
|
|
645
|
-
marketplace:
|
|
646
|
-
status:
|
|
715
|
+
sellerId: z17.string().min(1).max(50).optional().describe('Filter to a single connected Amazon seller account ID, e.g. "A1B2C3D4E5".'),
|
|
716
|
+
marketplace: z17.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.'),
|
|
717
|
+
status: z17.enum(ALERT_STATUSES).optional().describe(
|
|
647
718
|
"Lifecycle filter. `active` (default) returns unresolved alerts, `resolved` returns resolved alerts, `all` returns both. Dismissed alerts are never returned."
|
|
648
719
|
),
|
|
649
|
-
updatedSince:
|
|
720
|
+
updatedSince: z17.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(
|
|
650
721
|
"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."
|
|
651
722
|
),
|
|
652
|
-
currentPage:
|
|
653
|
-
pageSize:
|
|
723
|
+
currentPage: z17.number().int().min(1).optional().describe("Page number, 1-indexed. Defaults to 1."),
|
|
724
|
+
pageSize: z17.number().int().min(1).max(50).optional().describe("Items per page (max 50). Defaults to 20.")
|
|
654
725
|
};
|
|
655
726
|
|
|
656
727
|
// src/tools/list-indexing-issue-alerts.ts
|
|
@@ -659,6 +730,7 @@ var listIndexingIssueAlertsTool = {
|
|
|
659
730
|
title: "List Indexing-Issue Alerts",
|
|
660
731
|
description: "Use this to find products that may have lost search visibility. Retrieves a paginated list of indexing-issue alerts across the user's connected Amazon seller accounts \u2014 an alert fires when one of their ASINs is no longer indexed for its tracked keywords. By default returns active (unresolved) alerts from the last 30 days; filter by `sellerId`, `marketplace`, `status`, or `updatedSince` (for incremental polling). Each item includes id, asin, title, imageUrl, isParent, sellerId, marketplace, lastAlertedAt, and resolvedAt. Supports pagination metadata (currentPage, pageSize, total, lastPage, hasNext, hasPrev).",
|
|
661
732
|
inputSchema: alertsQueryInputSchema,
|
|
733
|
+
annotations: { readOnlyHint: true },
|
|
662
734
|
handler: async (args, ctx) => {
|
|
663
735
|
return await httpGet(
|
|
664
736
|
{ config: ctx.config, toolName: "list_indexing_issue_alerts" },
|
|
@@ -681,6 +753,7 @@ var listBlindSpendAlertsTool = {
|
|
|
681
753
|
title: "List Blind-Spend (Wasted-Spend) Alerts",
|
|
682
754
|
description: "Use this to find wasted PPC ad spend. Retrieves a paginated list of blind-spend alerts across the user's connected Amazon seller accounts \u2014 an alert flags ad spend on customer search terms that produced little or no sales. By default returns active (unresolved) alerts from the last 30 days; filter by `sellerId`, `marketplace`, `status`, or `updatedSince` (for incremental polling). Each item includes id, asin, title, imageUrl, sellerId, marketplace, lastAlertedAt, resolvedAt, wastedSpend (total ad spend across unresolved terms), totalKeywordCount, unresolvedKeywordCount, and searchTerms \u2014 the unresolved wasted-spend search terms, each with term, spend, sales, clicks, cvr (0-1), and impressions. Supports pagination metadata (currentPage, pageSize, total, lastPage, hasNext, hasPrev).",
|
|
683
755
|
inputSchema: alertsQueryInputSchema,
|
|
756
|
+
annotations: { readOnlyHint: true },
|
|
684
757
|
handler: async (args, ctx) => {
|
|
685
758
|
return await httpGet(
|
|
686
759
|
{ config: ctx.config, toolName: "list_blind_spend_alerts" },
|
|
@@ -698,32 +771,34 @@ var listBlindSpendAlertsTool = {
|
|
|
698
771
|
};
|
|
699
772
|
|
|
700
773
|
// src/tools/get-quota.ts
|
|
701
|
-
var
|
|
774
|
+
var inputSchema16 = {};
|
|
702
775
|
var getQuotaTool = {
|
|
703
776
|
name: "get_quota",
|
|
704
777
|
title: "Get Quota Usage",
|
|
705
778
|
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.",
|
|
706
|
-
inputSchema:
|
|
779
|
+
inputSchema: inputSchema16,
|
|
780
|
+
annotations: { readOnlyHint: true },
|
|
707
781
|
handler: async (_args, ctx) => {
|
|
708
782
|
return await httpGet({ config: ctx.config, toolName: "get_quota" }, "/v1/quota");
|
|
709
783
|
}
|
|
710
784
|
};
|
|
711
785
|
|
|
712
786
|
// src/tools/list-usage.ts
|
|
713
|
-
import { z as
|
|
714
|
-
var
|
|
715
|
-
type:
|
|
716
|
-
search:
|
|
717
|
-
startDate:
|
|
718
|
-
endDate:
|
|
719
|
-
currentPage:
|
|
720
|
-
pageSize:
|
|
787
|
+
import { z as z18 } from "zod";
|
|
788
|
+
var inputSchema17 = {
|
|
789
|
+
type: z18.enum(BILLABLE_FEATURE_TYPES).optional().describe("Filter to a single billable feature type. Omit to return all types."),
|
|
790
|
+
search: z18.string().min(1).optional().describe("Case-insensitive partial match on the user's name or email."),
|
|
791
|
+
startDate: z18.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."),
|
|
792
|
+
endDate: z18.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."),
|
|
793
|
+
currentPage: z18.number().int().min(1).optional().describe("Page number, 1-indexed. Defaults to 1."),
|
|
794
|
+
pageSize: z18.number().int().min(1).max(200).optional().describe("Items per page (max 200). Defaults to 50.")
|
|
721
795
|
};
|
|
722
796
|
var listUsageTool = {
|
|
723
797
|
name: "list_usage",
|
|
724
798
|
title: "List Billable Usage Logs",
|
|
725
799
|
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).",
|
|
726
|
-
inputSchema:
|
|
800
|
+
inputSchema: inputSchema17,
|
|
801
|
+
annotations: { readOnlyHint: true },
|
|
727
802
|
handler: async (args, ctx) => {
|
|
728
803
|
return await httpGet(
|
|
729
804
|
{ config: ctx.config, toolName: "list_usage" },
|
|
@@ -751,6 +826,7 @@ var allTools = [
|
|
|
751
826
|
getRankRadarDataTool,
|
|
752
827
|
createRankRadarTool,
|
|
753
828
|
createNicheDiveTool,
|
|
829
|
+
rediveNicheTool,
|
|
754
830
|
getDiveStatusTool,
|
|
755
831
|
listSellerProfilesTool,
|
|
756
832
|
getSellerCatalogTool,
|
|
@@ -763,6 +839,9 @@ var allTools = [
|
|
|
763
839
|
];
|
|
764
840
|
|
|
765
841
|
// src/server.ts
|
|
842
|
+
function requiredScope(tool) {
|
|
843
|
+
return tool.annotations?.readOnlyHint === true ? SCOPE_READ : SCOPE_WRITE;
|
|
844
|
+
}
|
|
766
845
|
function buildServer(config) {
|
|
767
846
|
const server = new McpServer({
|
|
768
847
|
name: "datadive",
|
|
@@ -778,6 +857,18 @@ function buildServer(config) {
|
|
|
778
857
|
annotations: tool.annotations
|
|
779
858
|
},
|
|
780
859
|
async (args) => {
|
|
860
|
+
const scope = requiredScope(tool);
|
|
861
|
+
if (config.scopes && !config.scopes.includes(scope)) {
|
|
862
|
+
return {
|
|
863
|
+
isError: true,
|
|
864
|
+
content: [
|
|
865
|
+
{
|
|
866
|
+
type: "text",
|
|
867
|
+
text: `Insufficient scope: this tool requires the "${scope}" scope, which was not granted to this connection. Reconnect DataDive and enable the missing permission to use it.`
|
|
868
|
+
}
|
|
869
|
+
]
|
|
870
|
+
};
|
|
871
|
+
}
|
|
781
872
|
try {
|
|
782
873
|
const data = await tool.handler(args, { config });
|
|
783
874
|
const content = [
|