@datadive-tools/mcp 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -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.7.0";
108
+ var version = "0.8.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
- "x-api-key": ctx.config.apiKey,
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
- "x-api-key": ctx.config.apiKey,
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" },
@@ -628,6 +641,7 @@ var getAsinInventoryDistributionTool = {
628
641
  title: "Get Per-Fulfillment-Center Inventory Distribution for an ASIN",
629
642
  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
643
  inputSchema: inputSchema14,
644
+ annotations: { readOnlyHint: true },
631
645
  handler: async (args, ctx) => {
632
646
  return await httpGet(
633
647
  { config: ctx.config, toolName: "get_asin_inventory_distribution" },
@@ -659,6 +673,7 @@ var listIndexingIssueAlertsTool = {
659
673
  title: "List Indexing-Issue Alerts",
660
674
  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
675
  inputSchema: alertsQueryInputSchema,
676
+ annotations: { readOnlyHint: true },
662
677
  handler: async (args, ctx) => {
663
678
  return await httpGet(
664
679
  { config: ctx.config, toolName: "list_indexing_issue_alerts" },
@@ -681,6 +696,7 @@ var listBlindSpendAlertsTool = {
681
696
  title: "List Blind-Spend (Wasted-Spend) Alerts",
682
697
  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
698
  inputSchema: alertsQueryInputSchema,
699
+ annotations: { readOnlyHint: true },
684
700
  handler: async (args, ctx) => {
685
701
  return await httpGet(
686
702
  { config: ctx.config, toolName: "list_blind_spend_alerts" },
@@ -704,6 +720,7 @@ var getQuotaTool = {
704
720
  title: "Get Quota Usage",
705
721
  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
722
  inputSchema: inputSchema15,
723
+ annotations: { readOnlyHint: true },
707
724
  handler: async (_args, ctx) => {
708
725
  return await httpGet({ config: ctx.config, toolName: "get_quota" }, "/v1/quota");
709
726
  }
@@ -724,6 +741,7 @@ var listUsageTool = {
724
741
  title: "List Billable Usage Logs",
725
742
  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
743
  inputSchema: inputSchema16,
744
+ annotations: { readOnlyHint: true },
727
745
  handler: async (args, ctx) => {
728
746
  return await httpGet(
729
747
  { config: ctx.config, toolName: "list_usage" },
@@ -763,6 +781,9 @@ var allTools = [
763
781
  ];
764
782
 
765
783
  // src/server.ts
784
+ function requiredScope(tool) {
785
+ return tool.annotations?.readOnlyHint === true ? SCOPE_READ : SCOPE_WRITE;
786
+ }
766
787
  function buildServer(config) {
767
788
  const server = new McpServer({
768
789
  name: "datadive",
@@ -778,6 +799,18 @@ function buildServer(config) {
778
799
  annotations: tool.annotations
779
800
  },
780
801
  async (args) => {
802
+ const scope = requiredScope(tool);
803
+ if (config.scopes && !config.scopes.includes(scope)) {
804
+ return {
805
+ isError: true,
806
+ content: [
807
+ {
808
+ type: "text",
809
+ 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.`
810
+ }
811
+ ]
812
+ };
813
+ }
781
814
  try {
782
815
  const data = await tool.handler(args, { config });
783
816
  const content = [