@datadive-tools/mcp 0.11.0 → 0.13.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 +9 -0
- package/dist/index.js +90 -27
- package/dist/index.js.map +1 -1
- package/dist/lib.d.ts +2 -1
- package/dist/lib.js +90 -27
- package/dist/lib.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -146,6 +146,14 @@ assistant to confirm the cost with you first:
|
|
|
146
146
|
|
|
147
147
|
Check your remaining balance any time with `get_quota`.
|
|
148
148
|
|
|
149
|
+
`create_rank_radar` uses that first, unconfirmed call to preview the creation
|
|
150
|
+
against the API, so the note it comes back with can tell you something specific
|
|
151
|
+
rather than only the token cost — for instance that the product family you asked
|
|
152
|
+
for is already tracked by another of your Rank Radars, which would spend Tracked
|
|
153
|
+
Search Terms on it twice. The preview creates nothing and spends nothing, and a
|
|
154
|
+
warning never blocks the creation: two Rank Radars on one family is how you
|
|
155
|
+
track two different sets of search terms and keep separate statistics.
|
|
156
|
+
|
|
149
157
|
The other write tools — `pause_rank_radar`, `resume_rank_radar` and the three
|
|
150
158
|
`*_rank_radar_search_terms` tools — need no confirmation. They only move Daily
|
|
151
159
|
Tracked Keywords capacity, which is freed again when you pause, and each is
|
|
@@ -197,6 +205,7 @@ specific HTTP status from the DataDive API.
|
|
|
197
205
|
|---|---|
|
|
198
206
|
| **Authentication failed: your DATADIVE_API_KEY is invalid or expired** | The key is wrong, deleted, or expired. Generate a new one at https://2.datadive.tools/api-key. |
|
|
199
207
|
| **Subscription is inactive or paused** | Resume billing at https://2.datadive.tools — the API key is valid but the subscription isn't active. |
|
|
208
|
+
| **Quota exceeded** / **… quota exceeded** | The subscription's quota for that billable feature (Dive tokens, Rank Radar tracked keywords, AI Copywriter prompts, …) is used up. The message names the feature, the usage, the next refresh date when there is one, and links to the subscription overview page where it can be raised. Don't retry until it has been. |
|
|
200
209
|
| **Forbidden** | The key is valid but doesn't have access to that resource. Usually a niche/rank-radar that belongs to a different org. |
|
|
201
210
|
| **Rate limit exceeded** | Wait a few seconds and retry. |
|
|
202
211
|
| **Bad request** | Check the parameters — the message echoes the server's validation error (e.g., `pageSize must not exceed 50`). |
|
package/dist/index.js
CHANGED
|
@@ -31,6 +31,13 @@ import { z } from "zod";
|
|
|
31
31
|
|
|
32
32
|
// src/http/errors.ts
|
|
33
33
|
var KEY_HELP_URL2 = "https://2.datadive.tools/api-key";
|
|
34
|
+
var FEATURE_LABELS = {
|
|
35
|
+
DIVED_ASINS: "Dive tokens",
|
|
36
|
+
RANK_RADAR_KEYWORDS: "Rank Radar tracked keywords",
|
|
37
|
+
PRODUCT_BRIEF_ASINS: "Product Brief ASINs",
|
|
38
|
+
AI_COPYWRITER_PROMPTS: "AI Copywriter prompts",
|
|
39
|
+
INDEXING_DIAGNOSIS: "Indexing Diagnosis checks"
|
|
40
|
+
};
|
|
34
41
|
var ApiError = class _ApiError extends Error {
|
|
35
42
|
kind;
|
|
36
43
|
status;
|
|
@@ -43,6 +50,9 @@ var ApiError = class _ApiError extends Error {
|
|
|
43
50
|
this.body = body;
|
|
44
51
|
}
|
|
45
52
|
static fromHttp(status, body) {
|
|
53
|
+
if (isQuotaExceededBody(body)) {
|
|
54
|
+
return new _ApiError("quota", status, quotaExceededMessage(body), body);
|
|
55
|
+
}
|
|
46
56
|
const serverMsg = extractMessage(body);
|
|
47
57
|
if (status === 400) {
|
|
48
58
|
return new _ApiError(
|
|
@@ -93,6 +103,35 @@ var ApiError = class _ApiError extends Error {
|
|
|
93
103
|
return new _ApiError("http", status, serverMsg ?? `HTTP ${status}`, body);
|
|
94
104
|
}
|
|
95
105
|
};
|
|
106
|
+
function isQuotaExceededBody(body) {
|
|
107
|
+
if (!body || typeof body !== "object") return false;
|
|
108
|
+
const b = body;
|
|
109
|
+
return b.error === "QUOTA_EXCEEDED" && typeof b.subscriptionUrl === "string";
|
|
110
|
+
}
|
|
111
|
+
function quotaExceededMessage(body) {
|
|
112
|
+
const lead = typeof body.message === "string" && body.message.length > 0 ? body.message : "Quota exceeded";
|
|
113
|
+
const feature = body.feature ? FEATURE_LABELS[body.feature] ?? body.feature : null;
|
|
114
|
+
const parts = [];
|
|
115
|
+
if (feature) {
|
|
116
|
+
parts.push(`This subscription has used up its ${feature} quota${usageSuffix(body)}.`);
|
|
117
|
+
} else {
|
|
118
|
+
parts.push(`This subscription has used up its quota for this action${usageSuffix(body)}.`);
|
|
119
|
+
}
|
|
120
|
+
if (body.nextRefreshDate) {
|
|
121
|
+
parts.push(`It refreshes on ${body.nextRefreshDate.slice(0, 10)}.`);
|
|
122
|
+
}
|
|
123
|
+
parts.push(
|
|
124
|
+
`Tell the user which quota ran out and that they can review or raise it on their subscription page: ${body.subscriptionUrl}`
|
|
125
|
+
);
|
|
126
|
+
parts.push("Do not retry this call until the quota has been raised.");
|
|
127
|
+
return `${lead}. ${parts.join(" ")}`;
|
|
128
|
+
}
|
|
129
|
+
function usageSuffix(body) {
|
|
130
|
+
if (typeof body.used === "number" && typeof body.capacity === "number") {
|
|
131
|
+
return ` (${body.used} of ${body.capacity} used)`;
|
|
132
|
+
}
|
|
133
|
+
return "";
|
|
134
|
+
}
|
|
96
135
|
function extractMessage(body) {
|
|
97
136
|
if (!body || typeof body !== "object") return null;
|
|
98
137
|
const b = body;
|
|
@@ -105,7 +144,7 @@ function extractMessage(body) {
|
|
|
105
144
|
}
|
|
106
145
|
|
|
107
146
|
// package.json
|
|
108
|
-
var version = "0.
|
|
147
|
+
var version = "0.13.0";
|
|
109
148
|
|
|
110
149
|
// src/http/client.ts
|
|
111
150
|
var PKG_VERSION = version;
|
|
@@ -241,20 +280,36 @@ function isObject(v) {
|
|
|
241
280
|
// src/tools/list-niches.ts
|
|
242
281
|
var LIST_NICHES_MAX_PAGE_SIZE = 50;
|
|
243
282
|
var inputSchema = {
|
|
244
|
-
currentPage: z.number().int().min(1).optional().describe("Page number, 1-indexed. Defaults to 1."),
|
|
245
|
-
pageSize: z.number().int().min(1).max(LIST_NICHES_MAX_PAGE_SIZE).optional().describe(`Items per page (max ${LIST_NICHES_MAX_PAGE_SIZE}). Defaults to 20.`)
|
|
283
|
+
currentPage: z.number().int().min(1).optional().describe("Page number, 1-indexed. Defaults to 1. Keep requesting the next page while `hasNext` is true."),
|
|
284
|
+
pageSize: z.number().int().min(1).max(LIST_NICHES_MAX_PAGE_SIZE).optional().describe(`Items per page (max ${LIST_NICHES_MAX_PAGE_SIZE}). Defaults to 20.`),
|
|
285
|
+
searchText: z.string().trim().min(1).max(200).optional().describe(
|
|
286
|
+
"Case-insensitive partial match on the niche label or hero keyword. Prefer this over paging through everything when the user names a niche."
|
|
287
|
+
),
|
|
288
|
+
searchAsin: z.string().trim().regex(/^[A-Za-z0-9]{10}$/, "must be a 10-character ASIN").optional().describe("Only niches whose competitor set contains this ASIN (10 characters, e.g. B08N5WRWNW)."),
|
|
289
|
+
orderBy: z.enum(["lastDived", "name"]).optional().describe(
|
|
290
|
+
"Sort field: `lastDived` (by latestResearchDate, newest first by default) or `name` (by nicheLabel, A\u2192Z by default). Defaults to `lastDived`."
|
|
291
|
+
),
|
|
292
|
+
sortOrder: z.enum(["ASC", "DESC"]).optional().describe("Sort direction. Defaults to DESC for `lastDived` and ASC for `name`.")
|
|
246
293
|
};
|
|
247
294
|
var listNichesTool = {
|
|
248
295
|
name: "list_niches",
|
|
249
296
|
title: "List DataDive Niches",
|
|
250
|
-
description:
|
|
297
|
+
description: `Use this first when the user asks about their niches, or to find a \`nicheId\` for use with \`get_niche_keywords\`, \`get_niche_competitors\`, or \`get_ranking_juice\`. Retrieves a paginated list of Niches, newest dive first. Each Niche represents a market segment or product category being tracked. Returns nicheId, heroKeyword, nicheLabel, marketplace (com/uk/de/...), and latestResearchDate per niche, plus pagination metadata (currentPage, pageSize, total, lastPage, hasNext, hasPrev). Narrow with \`searchText\` (label/keyword) or \`searchAsin\` (a competitor ASIN) instead of scanning pages; a page holds at most ${LIST_NICHES_MAX_PAGE_SIZE} niches, so the full list of a large account needs several calls.`,
|
|
251
298
|
inputSchema,
|
|
252
299
|
annotations: { readOnlyHint: true },
|
|
253
300
|
handler: async (args, ctx) => {
|
|
254
|
-
return await httpGet(
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
301
|
+
return await httpGet(
|
|
302
|
+
{ config: ctx.config, toolName: "list_niches" },
|
|
303
|
+
"/v1/niches",
|
|
304
|
+
{
|
|
305
|
+
currentPage: args.currentPage,
|
|
306
|
+
pageSize: args.pageSize,
|
|
307
|
+
searchText: args.searchText,
|
|
308
|
+
searchAsin: args.searchAsin,
|
|
309
|
+
orderBy: args.orderBy,
|
|
310
|
+
sortOrder: args.sortOrder
|
|
311
|
+
}
|
|
312
|
+
);
|
|
258
313
|
}
|
|
259
314
|
};
|
|
260
315
|
|
|
@@ -431,16 +486,24 @@ var getRankRadarDataTool = {
|
|
|
431
486
|
import { z as z8 } from "zod";
|
|
432
487
|
|
|
433
488
|
// src/tools/confirm.ts
|
|
434
|
-
function
|
|
435
|
-
|
|
489
|
+
function needsConfirmation(confirm, ctx) {
|
|
490
|
+
return !(confirm === true || ctx.config.autoConfirmWrites);
|
|
491
|
+
}
|
|
492
|
+
function confirmationRequired(costNote, warnings) {
|
|
436
493
|
return {
|
|
437
494
|
status: "confirmation_required",
|
|
438
495
|
message: "This action spends billable tokens and cannot be undone. Confirm the cost with the user, then call again with `confirm: true` to proceed.",
|
|
439
|
-
costNote
|
|
496
|
+
costNote,
|
|
497
|
+
...warnings && warnings.length > 0 ? { warnings } : {}
|
|
440
498
|
};
|
|
441
499
|
}
|
|
500
|
+
function requireConfirmation(confirm, ctx, costNote) {
|
|
501
|
+
if (!needsConfirmation(confirm, ctx)) return null;
|
|
502
|
+
return confirmationRequired(costNote);
|
|
503
|
+
}
|
|
442
504
|
|
|
443
505
|
// src/tools/create-rank-radar.ts
|
|
506
|
+
var PATH = "/v1/niches/rank-radars";
|
|
444
507
|
var inputSchema8 = {
|
|
445
508
|
asin: z8.string().min(1).describe("The ASIN to track keyword rankings for."),
|
|
446
509
|
numberOfKeywords: z8.number().int().min(1).describe("How many keywords to track (minimum 1). More keywords = broader tracking but more Search Term tokens."),
|
|
@@ -452,28 +515,28 @@ var inputSchema8 = {
|
|
|
452
515
|
var createRankRadarTool = {
|
|
453
516
|
name: "create_rank_radar",
|
|
454
517
|
title: "Create a Rank Radar",
|
|
455
|
-
description: "Use this to start tracking organic and sponsored keyword rankings for an ASIN within a niche. \u26A0\uFE0F Spends Search Term tokens (cost scales with `numberOfKeywords`) and cannot be undone \u2014 set `confirm: true` only after the user approves the cost. Not safe to retry: each call spends tokens again and creates a separate Rank Radar \u2014 if a call errors or times out, check `list_rank_radars` instead of re-calling. Returns the new `rankRadarId`; read its data later with `get_rank_radar_data`. Requires `asin`, `numberOfKeywords` (min 1), and a `nicheId` from `list_niches`.",
|
|
518
|
+
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`.",
|
|
456
519
|
inputSchema: inputSchema8,
|
|
457
520
|
// destructiveHint is technically "additive" here (this creates a new Rank Radar, it
|
|
458
521
|
// doesn't destroy data), but we flag it destructive so clients prompt before the
|
|
459
522
|
// irreversible token spend. The confirm gate enforces this server-side regardless.
|
|
460
523
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
461
524
|
handler: async (args, ctx) => {
|
|
462
|
-
const
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
);
|
|
525
|
+
const request = { config: ctx.config, toolName: "create_rank_radar" };
|
|
526
|
+
const body = {
|
|
527
|
+
asin: args.asin,
|
|
528
|
+
numberOfKeywords: args.numberOfKeywords,
|
|
529
|
+
nicheId: args.nicheId
|
|
530
|
+
};
|
|
531
|
+
if (needsConfirmation(args.confirm, ctx)) {
|
|
532
|
+
const preview = await httpPost(request, PATH, { ...body, dryRun: true });
|
|
533
|
+
if (preview.rankRadarId != null) return preview;
|
|
534
|
+
return confirmationRequired(
|
|
535
|
+
`Creating this Rank Radar consumes Search Term tokens \u2014 one per keyword tracked (${args.numberOfKeywords} requested).`,
|
|
536
|
+
preview.warnings
|
|
537
|
+
);
|
|
538
|
+
}
|
|
539
|
+
return await httpPost(request, PATH, body);
|
|
477
540
|
}
|
|
478
541
|
};
|
|
479
542
|
|