@datadive-tools/mcp 0.11.0 → 0.12.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 +1 -0
- package/dist/index.js +63 -8
- package/dist/index.js.map +1 -1
- package/dist/lib.d.ts +2 -1
- package/dist/lib.js +63 -8
- 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.12.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
|
|