@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/dist/lib.d.ts
CHANGED
|
@@ -157,8 +157,9 @@ declare const allTools: ReadonlyArray<AnyTool>;
|
|
|
157
157
|
* Source of truth for status semantics:
|
|
158
158
|
* datadive-backend/src/external-api/auth/api-key.guard.ts (401/402 logic)
|
|
159
159
|
* datadive-backend/src/external-api/external-api-v1.controller.ts (per-route docs)
|
|
160
|
+
* datadive-backend/src/microservices/token/quota-exceeded.error.ts (QUOTA_EXCEEDED body)
|
|
160
161
|
*/
|
|
161
|
-
type ApiErrorKind = "auth" | "payment" | "forbidden" | "not_found" | "bad_request" | "rate_limit" | "server" | "network" | "http";
|
|
162
|
+
type ApiErrorKind = "auth" | "payment" | "quota" | "forbidden" | "not_found" | "bad_request" | "rate_limit" | "server" | "network" | "http";
|
|
162
163
|
declare class ApiError extends Error {
|
|
163
164
|
readonly kind: ApiErrorKind;
|
|
164
165
|
readonly status: number;
|
package/dist/lib.js
CHANGED
|
@@ -26,6 +26,13 @@ import { z } from "zod";
|
|
|
26
26
|
|
|
27
27
|
// src/http/errors.ts
|
|
28
28
|
var KEY_HELP_URL2 = "https://2.datadive.tools/api-key";
|
|
29
|
+
var FEATURE_LABELS = {
|
|
30
|
+
DIVED_ASINS: "Dive tokens",
|
|
31
|
+
RANK_RADAR_KEYWORDS: "Rank Radar tracked keywords",
|
|
32
|
+
PRODUCT_BRIEF_ASINS: "Product Brief ASINs",
|
|
33
|
+
AI_COPYWRITER_PROMPTS: "AI Copywriter prompts",
|
|
34
|
+
INDEXING_DIAGNOSIS: "Indexing Diagnosis checks"
|
|
35
|
+
};
|
|
29
36
|
var ApiError = class _ApiError extends Error {
|
|
30
37
|
kind;
|
|
31
38
|
status;
|
|
@@ -38,6 +45,9 @@ var ApiError = class _ApiError extends Error {
|
|
|
38
45
|
this.body = body;
|
|
39
46
|
}
|
|
40
47
|
static fromHttp(status, body) {
|
|
48
|
+
if (isQuotaExceededBody(body)) {
|
|
49
|
+
return new _ApiError("quota", status, quotaExceededMessage(body), body);
|
|
50
|
+
}
|
|
41
51
|
const serverMsg = extractMessage(body);
|
|
42
52
|
if (status === 400) {
|
|
43
53
|
return new _ApiError(
|
|
@@ -88,6 +98,35 @@ var ApiError = class _ApiError extends Error {
|
|
|
88
98
|
return new _ApiError("http", status, serverMsg ?? `HTTP ${status}`, body);
|
|
89
99
|
}
|
|
90
100
|
};
|
|
101
|
+
function isQuotaExceededBody(body) {
|
|
102
|
+
if (!body || typeof body !== "object") return false;
|
|
103
|
+
const b = body;
|
|
104
|
+
return b.error === "QUOTA_EXCEEDED" && typeof b.subscriptionUrl === "string";
|
|
105
|
+
}
|
|
106
|
+
function quotaExceededMessage(body) {
|
|
107
|
+
const lead = typeof body.message === "string" && body.message.length > 0 ? body.message : "Quota exceeded";
|
|
108
|
+
const feature = body.feature ? FEATURE_LABELS[body.feature] ?? body.feature : null;
|
|
109
|
+
const parts = [];
|
|
110
|
+
if (feature) {
|
|
111
|
+
parts.push(`This subscription has used up its ${feature} quota${usageSuffix(body)}.`);
|
|
112
|
+
} else {
|
|
113
|
+
parts.push(`This subscription has used up its quota for this action${usageSuffix(body)}.`);
|
|
114
|
+
}
|
|
115
|
+
if (body.nextRefreshDate) {
|
|
116
|
+
parts.push(`It refreshes on ${body.nextRefreshDate.slice(0, 10)}.`);
|
|
117
|
+
}
|
|
118
|
+
parts.push(
|
|
119
|
+
`Tell the user which quota ran out and that they can review or raise it on their subscription page: ${body.subscriptionUrl}`
|
|
120
|
+
);
|
|
121
|
+
parts.push("Do not retry this call until the quota has been raised.");
|
|
122
|
+
return `${lead}. ${parts.join(" ")}`;
|
|
123
|
+
}
|
|
124
|
+
function usageSuffix(body) {
|
|
125
|
+
if (typeof body.used === "number" && typeof body.capacity === "number") {
|
|
126
|
+
return ` (${body.used} of ${body.capacity} used)`;
|
|
127
|
+
}
|
|
128
|
+
return "";
|
|
129
|
+
}
|
|
91
130
|
function extractMessage(body) {
|
|
92
131
|
if (!body || typeof body !== "object") return null;
|
|
93
132
|
const b = body;
|
|
@@ -100,7 +139,7 @@ function extractMessage(body) {
|
|
|
100
139
|
}
|
|
101
140
|
|
|
102
141
|
// package.json
|
|
103
|
-
var version = "0.
|
|
142
|
+
var version = "0.13.0";
|
|
104
143
|
|
|
105
144
|
// src/http/client.ts
|
|
106
145
|
var PKG_VERSION = version;
|
|
@@ -236,20 +275,36 @@ function isObject(v) {
|
|
|
236
275
|
// src/tools/list-niches.ts
|
|
237
276
|
var LIST_NICHES_MAX_PAGE_SIZE = 50;
|
|
238
277
|
var inputSchema = {
|
|
239
|
-
currentPage: z.number().int().min(1).optional().describe("Page number, 1-indexed. Defaults to 1."),
|
|
240
|
-
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.`)
|
|
278
|
+
currentPage: z.number().int().min(1).optional().describe("Page number, 1-indexed. Defaults to 1. Keep requesting the next page while `hasNext` is true."),
|
|
279
|
+
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.`),
|
|
280
|
+
searchText: z.string().trim().min(1).max(200).optional().describe(
|
|
281
|
+
"Case-insensitive partial match on the niche label or hero keyword. Prefer this over paging through everything when the user names a niche."
|
|
282
|
+
),
|
|
283
|
+
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)."),
|
|
284
|
+
orderBy: z.enum(["lastDived", "name"]).optional().describe(
|
|
285
|
+
"Sort field: `lastDived` (by latestResearchDate, newest first by default) or `name` (by nicheLabel, A\u2192Z by default). Defaults to `lastDived`."
|
|
286
|
+
),
|
|
287
|
+
sortOrder: z.enum(["ASC", "DESC"]).optional().describe("Sort direction. Defaults to DESC for `lastDived` and ASC for `name`.")
|
|
241
288
|
};
|
|
242
289
|
var listNichesTool = {
|
|
243
290
|
name: "list_niches",
|
|
244
291
|
title: "List DataDive Niches",
|
|
245
|
-
description:
|
|
292
|
+
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.`,
|
|
246
293
|
inputSchema,
|
|
247
294
|
annotations: { readOnlyHint: true },
|
|
248
295
|
handler: async (args, ctx) => {
|
|
249
|
-
return await httpGet(
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
296
|
+
return await httpGet(
|
|
297
|
+
{ config: ctx.config, toolName: "list_niches" },
|
|
298
|
+
"/v1/niches",
|
|
299
|
+
{
|
|
300
|
+
currentPage: args.currentPage,
|
|
301
|
+
pageSize: args.pageSize,
|
|
302
|
+
searchText: args.searchText,
|
|
303
|
+
searchAsin: args.searchAsin,
|
|
304
|
+
orderBy: args.orderBy,
|
|
305
|
+
sortOrder: args.sortOrder
|
|
306
|
+
}
|
|
307
|
+
);
|
|
253
308
|
}
|
|
254
309
|
};
|
|
255
310
|
|
|
@@ -426,16 +481,24 @@ var getRankRadarDataTool = {
|
|
|
426
481
|
import { z as z8 } from "zod";
|
|
427
482
|
|
|
428
483
|
// src/tools/confirm.ts
|
|
429
|
-
function
|
|
430
|
-
|
|
484
|
+
function needsConfirmation(confirm, ctx) {
|
|
485
|
+
return !(confirm === true || ctx.config.autoConfirmWrites);
|
|
486
|
+
}
|
|
487
|
+
function confirmationRequired(costNote, warnings) {
|
|
431
488
|
return {
|
|
432
489
|
status: "confirmation_required",
|
|
433
490
|
message: "This action spends billable tokens and cannot be undone. Confirm the cost with the user, then call again with `confirm: true` to proceed.",
|
|
434
|
-
costNote
|
|
491
|
+
costNote,
|
|
492
|
+
...warnings && warnings.length > 0 ? { warnings } : {}
|
|
435
493
|
};
|
|
436
494
|
}
|
|
495
|
+
function requireConfirmation(confirm, ctx, costNote) {
|
|
496
|
+
if (!needsConfirmation(confirm, ctx)) return null;
|
|
497
|
+
return confirmationRequired(costNote);
|
|
498
|
+
}
|
|
437
499
|
|
|
438
500
|
// src/tools/create-rank-radar.ts
|
|
501
|
+
var PATH = "/v1/niches/rank-radars";
|
|
439
502
|
var inputSchema8 = {
|
|
440
503
|
asin: z8.string().min(1).describe("The ASIN to track keyword rankings for."),
|
|
441
504
|
numberOfKeywords: z8.number().int().min(1).describe("How many keywords to track (minimum 1). More keywords = broader tracking but more Search Term tokens."),
|
|
@@ -447,28 +510,28 @@ var inputSchema8 = {
|
|
|
447
510
|
var createRankRadarTool = {
|
|
448
511
|
name: "create_rank_radar",
|
|
449
512
|
title: "Create a Rank Radar",
|
|
450
|
-
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`.",
|
|
513
|
+
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`.",
|
|
451
514
|
inputSchema: inputSchema8,
|
|
452
515
|
// destructiveHint is technically "additive" here (this creates a new Rank Radar, it
|
|
453
516
|
// doesn't destroy data), but we flag it destructive so clients prompt before the
|
|
454
517
|
// irreversible token spend. The confirm gate enforces this server-side regardless.
|
|
455
518
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
456
519
|
handler: async (args, ctx) => {
|
|
457
|
-
const
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
);
|
|
520
|
+
const request = { config: ctx.config, toolName: "create_rank_radar" };
|
|
521
|
+
const body = {
|
|
522
|
+
asin: args.asin,
|
|
523
|
+
numberOfKeywords: args.numberOfKeywords,
|
|
524
|
+
nicheId: args.nicheId
|
|
525
|
+
};
|
|
526
|
+
if (needsConfirmation(args.confirm, ctx)) {
|
|
527
|
+
const preview = await httpPost(request, PATH, { ...body, dryRun: true });
|
|
528
|
+
if (preview.rankRadarId != null) return preview;
|
|
529
|
+
return confirmationRequired(
|
|
530
|
+
`Creating this Rank Radar consumes Search Term tokens \u2014 one per keyword tracked (${args.numberOfKeywords} requested).`,
|
|
531
|
+
preview.warnings
|
|
532
|
+
);
|
|
533
|
+
}
|
|
534
|
+
return await httpPost(request, PATH, body);
|
|
472
535
|
}
|
|
473
536
|
};
|
|
474
537
|
|